Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThorin <toz@thorin-kerrs-macbook-3.local>2010-03-19 03:51:27 (GMT)
committer Thorin <toz@thorin-kerrs-macbook-3.local>2010-03-19 03:51:27 (GMT)
commit76ca747b364bbfa06bf0869f7e62df2947672d48 (patch)
tree8e8dea41a08bf99af0a98dcf82e8ddcc5f95098f
Create repository and load
-rw-r--r--COPYING339
-rwxr-xr-xCity/City.py110
-rwxr-xr-xCity/CsHelpers.py194
-rwxr-xr-xCity/CsSched.py143
-rwxr-xr-xCity/Images/Bass.pngbin0 -> 2349 bytes
-rwxr-xr-xCity/Images/Chords.pngbin0 -> 1935 bytes
-rwxr-xr-xCity/Images/Drums.pngbin0 -> 3094 bytes
-rwxr-xr-xCity/Images/Lead.pngbin0 -> 2233 bytes
-rwxr-xr-xCity/Images/Mic.pngbin0 -> 2290 bytes
-rwxr-xr-xCity/Images/Video.pngbin0 -> 2793 bytes
-rwxr-xr-xCity/Images/camera-busy.svg15
-rwxr-xr-xCity/Images/camera-external.svg15
-rw-r--r--City/Images/jam2jamXO_2.pngbin0 -> 37883 bytes
-rwxr-xr-xCity/Images/media-playback-pause.svg9
-rwxr-xr-xCity/Images/media-playback-start.svg15
-rwxr-xr-xCity/OrcBuilder.py342
-rwxr-xr-xCity/Parameters.py128
-rw-r--r--City/Scenes/Blues/AudioFiles/BassAudio/BassC2[41-48-50].aifbin0 -> 101158 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/BassAudio/BassC3[59-60-62].aifbin0 -> 103186 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/BassAudio/BassC4[70-72-78].aifbin0 -> 100678 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/BassAudio/BassF#2[51-54-58].aifbin0 -> 100048 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/ChordsAudio/epnoC4[70-72-73].aifbin0 -> 72482 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/ChordsAudio/epnoC5[82-84-90].aifbin0 -> 72654 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/ChordsAudio/epnoE3[63-64-65].aifbin0 -> 72798 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/ChordsAudio/epnoE4[74-76-77].aifbin0 -> 72236 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/DrumsAudio/C1_kick_drum.aifbin0 -> 12670 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/DrumsAudio/D1_snare_drum.aifbin0 -> 15168 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/DrumsAudio/F#1_closed_hi_hat.aifbin0 -> 6302 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/DrumsAudio/Tamborine 22 16 Mono.aifbin0 -> 21174 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/LeadAudio/B2_guitar.aifbin0 -> 50070 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/LeadAudio/B3_guitar.aifbin0 -> 50412 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/LeadAudio/E2_guitar.aifbin0 -> 51016 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/LeadAudio/E3_guitar.aifbin0 -> 50324 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/LeadAudio/E4_guitar.aifbin0 -> 49942 bytes
-rw-r--r--City/Scenes/Blues/AudioFiles/LeadAudio/E5_guitar.aifbin0 -> 51070 bytes
-rw-r--r--City/Scenes/Blues/Blues_Data.txt4
-rw-r--r--City/Scenes/Blues/MidiFiles/Bass.midbin0 -> 738 bytes
-rw-r--r--City/Scenes/Blues/MidiFiles/Chords.midbin0 -> 990 bytes
-rw-r--r--City/Scenes/Blues/MidiFiles/Drums.midbin0 -> 3593 bytes
-rw-r--r--City/Scenes/Blues/MidiFiles/Lead.midbin0 -> 906 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/BassAudio/Dance1Bass[28-36-39]mn_000.aifbin0 -> 72074 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/BassAudio/Dance1Bass[40-48-51]mn_000.aifbin0 -> 68930 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/BassAudio/Dance1Bass[52-60-63]mn_000.aifbin0 -> 70106 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[100-108-111]mn_000.aifbin0 -> 252726 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[64-72-75]mn_000.aifbin0 -> 252710 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[76-84-87]mn_000.aifbin0 -> 252726 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[88-96-99]mn_000.aifbin0 -> 252726 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/DrumsAudio/Dance1909kick7[36-36-36].aifbin0 -> 23554 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/DrumsAudio/Dance1909snare3[38-38-38].aifbin0 -> 16590 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/DrumsAudio/Dance1SHAKER_1[46-46-46].aifbin0 -> 4660 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/DrumsAudio/Dance1closedhat11[39-39-39].aifbin0 -> 10838 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/DrumsAudio/Dance1openedhat4[42-42-42].aifbin0 -> 28466 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/LeadAudio/keys-good[28-36-39]_001_001mn_000.aifbin0 -> 32160 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/LeadAudio/keys-good[40-48-51]_001_001mn_000.aifbin0 -> 32428 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/LeadAudio/keys-good[52-60-63]_001_001mn_000.aifbin0 -> 32684 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/LeadAudio/keys-good[64-72-75]_001_001mn_000.aifbin0 -> 31904 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/LeadAudio/keys-good[76-84-87]_001_001mn_000.aifbin0 -> 32428 bytes
-rwxr-xr-xCity/Scenes/City/AudioFiles/LeadAudio/keys-good[88-96-99]_001_001mn_000.aifbin0 -> 31904 bytes
-rw-r--r--City/Scenes/City/City_Data.txt8
-rwxr-xr-xCity/Scenes/City/MidiFiles/Bass.midbin0 -> 335 bytes
-rwxr-xr-xCity/Scenes/City/MidiFiles/Chords.midbin0 -> 1075 bytes
-rwxr-xr-xCity/Scenes/City/MidiFiles/Drums.midbin0 -> 1952 bytes
-rwxr-xr-xCity/Scenes/City/MidiFiles/Lead.midbin0 -> 635 bytes
-rw-r--r--City/Scenes/Country/AudioFiles/BassAudio/BassC2[41-48-50].aifbin0 -> 101158 bytes
-rw-r--r--City/Scenes/Country/AudioFiles/BassAudio/BassC3[59-60-62].aifbin0 -> 103186 bytes
-rw-r--r--City/Scenes/Country/AudioFiles/BassAudio/BassC4[70-72-78].aifbin0 -> 100678 bytes
-rw-r--r--City/Scenes/Country/AudioFiles/BassAudio/BassF#2[51-54-58].aifbin0 -> 100048 bytes
-rw-r--r--City/Scenes/Country/AudioFiles/ChordsAudio/epnoC4[70-72-73].aifbin0 -> 72482 bytes
-rw-r--r--City/Scenes/Country/AudioFiles/ChordsAudio/epnoC5[82-84-90].aifbin0 -> 72654 bytes
-rw-r--r--City/Scenes/Country/AudioFiles/ChordsAudio/epnoE3[63-64-65].aifbin0 -> 72798 bytes
-rw-r--r--City/Scenes/Country/AudioFiles/ChordsAudio/epnoE4[74-76-77].aifbin0 -> 72236 bytes
-rwxr-xr-xCity/Scenes/Country/AudioFiles/DrumsAudio/C#1_rim_shot.aifbin0 -> 9447 bytes
-rw-r--r--City/Scenes/Country/AudioFiles/DrumsAudio/C1_kick_drum.aifbin0 -> 12739 bytes
-rwxr-xr-xCity/Scenes/Country/AudioFiles/DrumsAudio/D1_snare_drum.aifbin0 -> 15236 bytes
-rwxr-xr-xCity/Scenes/Country/AudioFiles/DrumsAudio/F#1_closed_hi_hat.aifbin0 -> 6371 bytes
-rw-r--r--City/Scenes/Country/AudioFiles/LeadAudio/B2_guitar.aifbin0 -> 50070 bytes
-rw-r--r--City/Scenes/Country/AudioFiles/LeadAudio/B3_guitar.aifbin0 -> 50412 bytes
-rw-r--r--City/Scenes/Country/AudioFiles/LeadAudio/E2_guitar.aifbin0 -> 51016 bytes
-rw-r--r--City/Scenes/Country/AudioFiles/LeadAudio/E3_guitar.aifbin0 -> 50324 bytes
-rw-r--r--City/Scenes/Country/AudioFiles/LeadAudio/E4_guitar.aifbin0 -> 49942 bytes
-rw-r--r--City/Scenes/Country/AudioFiles/LeadAudio/E5_guitar.aifbin0 -> 51070 bytes
-rw-r--r--City/Scenes/Country/Country_Data.txt5
-rwxr-xr-xCity/Scenes/Country/MidiFiles/Bass.midbin0 -> 1307 bytes
-rwxr-xr-xCity/Scenes/Country/MidiFiles/Chords.midbin0 -> 1357 bytes
-rwxr-xr-xCity/Scenes/Country/MidiFiles/Drums.midbin0 -> 3164 bytes
-rwxr-xr-xCity/Scenes/Country/MidiFiles/Lead.midbin0 -> 1085 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/BassAudio/BassC2[41-48-50].aifbin0 -> 101130 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/BassAudio/BassC3[59-60-62].aifbin0 -> 103158 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/BassAudio/BassC4[70-72-78].aifbin0 -> 100650 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/BassAudio/BassF#2[51-54-58].aifbin0 -> 100020 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/ChordsAudio/Organ C4 22 16 mono.aifbin0 -> 20982 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/ChordsAudio/Organ C5 22 16 mono.aifbin0 -> 20364 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/ChordsAudio/Organ G3 22 16 mono.aifbin0 -> 21540 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/ChordsAudio/Organ G4 22 16 mono.aifbin0 -> 19980 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/DrumsAudio/Bongo C#3 22 16 Mono.aifbin0 -> 7190 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/DrumsAudio/Bongo C3 22 16 Mono.aifbin0 -> 5530 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/DrumsAudio/Bongo D3 22 16 Mono.aifbin0 -> 4432 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/DrumsAudio/ClosedhatF#1.aifbin0 -> 5096 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/DrumsAudio/Kick-C1.aifbin0 -> 14366 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/DrumsAudio/Shaker 22 16 Mono.aifbin0 -> 13018 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/DrumsAudio/Snare-D1.aifbin0 -> 30086 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/DrumsAudio/TambF#2.aifbin0 -> 27936 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets A4 22 16 mono.aifbin0 -> 28164 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets C4 22 16 mono.aifbin0 -> 31100 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets E3 22 16 mono.aifbin0 -> 31846 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets F4 22 16 mono.aifbin0 -> 31886 bytes
-rw-r--r--City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets G3 22 16 mono.aifbin0 -> 32132 bytes
-rw-r--r--City/Scenes/Latin/Latin_Data.txt3
-rw-r--r--City/Scenes/Latin/MidiFiles/Bass.midbin0 -> 614 bytes
-rw-r--r--City/Scenes/Latin/MidiFiles/Chords.midbin0 -> 764 bytes
-rw-r--r--City/Scenes/Latin/MidiFiles/Drums.midbin0 -> 2919 bytes
-rw-r--r--City/Scenes/Latin/MidiFiles/Lead.midbin0 -> 844 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/BassAudio/BassC2[41-48-50].aifbin0 -> 101158 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/BassAudio/BassC3[59-60-62].aifbin0 -> 103186 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/BassAudio/BassC4[70-72-78].aifbin0 -> 100678 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/BassAudio/BassF#2[51-54-58].aifbin0 -> 100048 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoC4[70-72-73].aifbin0 -> 72482 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoC5[82-84-90].aifbin0 -> 72654 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoE3[63-64-65].aifbin0 -> 72798 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoE4[74-76-77].aifbin0 -> 72236 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/DrumsAudio/ClosedhatF#1.aifbin0 -> 5096 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/DrumsAudio/Kick-C1.aifbin0 -> 14366 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/DrumsAudio/OpenhatA#1.aifbin0 -> 112568 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/DrumsAudio/Snare-D1.aifbin0 -> 30086 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/DrumsAudio/TambF#2.aifbin0 -> 27936 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/LeadAudio/B2_guitar.aifbin0 -> 50070 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/LeadAudio/B3_guitar.aifbin0 -> 50412 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/LeadAudio/E2_guitar.aifbin0 -> 51016 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/LeadAudio/E3_guitar.aifbin0 -> 50324 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/LeadAudio/E4_guitar.aifbin0 -> 49942 bytes
-rw-r--r--City/Scenes/Reggae/AudioFiles/LeadAudio/E5_guitar.aifbin0 -> 51070 bytes
-rw-r--r--City/Scenes/Reggae/MidiFiles/Bass.midbin0 -> 484 bytes
-rw-r--r--City/Scenes/Reggae/MidiFiles/Chords.midbin0 -> 947 bytes
-rw-r--r--City/Scenes/Reggae/MidiFiles/Drums.midbin0 -> 1638 bytes
-rw-r--r--City/Scenes/Reggae/MidiFiles/Lead.midbin0 -> 764 bytes
-rw-r--r--City/Scenes/Reggae/Reggae_Data.txt3
-rwxr-xr-xCity/Tracks.py512
-rwxr-xr-xCity/__init__.py0
-rwxr-xr-xCity/midiImport.py379
-rwxr-xr-xCity/setup.py4
-rw-r--r--InstrumentPanel.py19
-rwxr-xr-xJ2JToolbar.py301
-rw-r--r--MANIFEST175
-rwxr-xr-xMANIFEST.in6
-rwxr-xr-xNEWS1
-rwxr-xr-xPOTFILES.in1
-rw-r--r--README9
-rwxr-xr-xactivity.py86
-rwxr-xr-xactivity/activity.info7
-rwxr-xr-xactivity/activity.svg24
-rwxr-xr-xarrow40b-mask.xbm20
-rwxr-xr-xarrow40b.xbm20
-rwxr-xr-xolpcgames/COPYING24
-rwxr-xr-xolpcgames/__init__.py102
-rwxr-xr-xolpcgames/_cairoimage.py135
-rwxr-xr-xolpcgames/_gtkmain.py70
-rwxr-xr-xolpcgames/_version.py2
-rwxr-xr-xolpcgames/activity.py241
-rwxr-xr-xolpcgames/buildmanifest.py33
-rwxr-xr-xolpcgames/camera.py221
-rwxr-xr-xolpcgames/canvas.py171
-rwxr-xr-xolpcgames/data/__init__.py36
-rwxr-xr-xolpcgames/data/sleeping_svg.py61
-rwxr-xr-xolpcgames/dbusproxy.py93
-rwxr-xr-xolpcgames/eventwrap.py388
-rwxr-xr-xolpcgames/gtkEvent.py289
-rwxr-xr-xolpcgames/mesh.py584
-rwxr-xr-xolpcgames/pangofont.py346
-rwxr-xr-xolpcgames/pausescreen.py116
-rwxr-xr-xolpcgames/svgsprite.py84
-rwxr-xr-xolpcgames/textsprite.py40
-rwxr-xr-xolpcgames/util.py79
-rwxr-xr-xolpcgames/video.py178
-rw-r--r--po/POTFILES.in5
-rw-r--r--run.py553
-rwxr-xr-xsetup.py4
176 files changed, 6752 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..2070779
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ Appendix: How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 20yy <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/City/City.py b/City/City.py
new file mode 100755
index 0000000..766206d
--- /dev/null
+++ b/City/City.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+"""
+Turn all of this into executable functions
+Then move it to run.py
+"""
+
+from CsHelpers import *
+import CsSched, Parameters, OrcBuilder, Tracks
+import random
+
+class ScenePlayer( SceneData ):
+ def __init__(self, scene_name = 'City', key = 'A', mode = 'minor', tempo = 120, defaults = {}):
+ SceneData.__init__(self, scene_name)
+ self.tempo = tempo
+ self.Csynth = CsSched.Csound()
+ self.TimeQueue = CsSched.Sched()
+ self.Params = Parameters.Perimeter()
+ self.orc = OrcBuilder.OrcConstructor()
+ self.makeOrc()
+ self.cs = CsSched.CsoundPerformer(self.TimeQueue, self.orc, self.Csynth.csound)
+ self.Params.csoundChannels(self.cs)
+ self.loadTracks(key, mode)
+ self.setParameters(defaults)
+ def setParameters(self, pdict):
+ "dict is a dictionary in the form: {Instrument:{parameter:value}}"
+ for i in pdict:
+ for p in pdict[i]:
+ print "setting: ", p,i,pdict[i][p]
+ self.Params.setValue(p,i,pdict[i][p])
+ def makeOrc(self, sr = 22050, ksmps = 128):
+ orc = self.orc
+ orc.sr = sr
+ orc.ksmps = ksmps
+ lookuptabs = [OrcBuilder.orcLoadSamples(orc, self.Csynth, instr+"Lookup", eval("self."+instr+"AudioPath")) for instr in INAMES]
+ ftabs = [OrcBuilder.FtableBreakPoint("rvbc1", -558, -594, -638, -678, -711, -745, -778, -808, 0.8, 0.79, 0.78, 0.77, 0.76, 0.75, 0.74, 0.73),
+ OrcBuilder.FtableBreakPoint("rvbc2", -517, -540, -656, -699, -752, -799, -818, -841, 0.8, 0.79, 0.78, 0.77, 0.76, 0.75, 0.74, 0.73),
+ OrcBuilder.FtableBreakPoint("rvba1", -278, -220, -170, -122, 0.4, 0.52, 0.64, 0.76),
+ OrcBuilder.FtableBreakPoint("rvba2", -333, -263, -166, -105, 0.5, 0.52, 0.64, 0.76)]
+ orc.insertftables(*ftabs)
+ volumechans = self.commonChannels("Volume", 0.8)
+ timbrechans = self.commonChannels("Timbre", 0.5)
+ orc.insertChannels(*volumechans)
+ orc.insertChannels(*timbrechans)
+ setlevels = OrcBuilder.OrcSetLevelInstrument()
+ setlevels.effect = True
+ mixerout = OrcBuilder.OrcMixoutInstrument()
+ mixerout.effect = True
+ samplerbody = """
+ idur = p3
+ iamp = p4 * (0dbfs / 127)
+ kcps init cpsmidinn(p5)
+ isamp table p5, %s
+ a1 loscil iamp, kcps, isamp
+ a1 dcblock a1
+ kdeclick linseg 0, 0.008, 1, idur - 0.05 - 0.008, 1, 0.05, 0
+ a1 = a1 * kdeclick """
+ timbrebody = ("""
+ idur = p3
+ ires = 5.75
+ kfco expcurve %s + 0.01, 17
+ kfco = kfco * """ + str(orc.sr*0.5 * 0.45) + """ + 1200
+ a2 rezzy a1, kfco, ires
+ a1 balance a2, a1
+ a1 = a1 * 0.2 + a2 * 0.4
+ """)
+ SamplerInstruments, TimbreInstruments = [OrcBuilder.OrcInstrument(i+'Sampler') for i in INAMES], [OrcBuilder.OrcInstrument(i+'Timbre') for i in INAMES]
+ for Si, Ti in zip(SamplerInstruments, TimbreInstruments):
+ Si.insertLine(samplerbody % [x for x in [n.varname() for n in lookuptabs] if x.__contains__(Si.name[:-7])][0])
+ Ti.insertLine(timbrebody % [y for y in [j.varname() for j in timbrechans] if y.__contains__(Ti.name[:-6])][0])
+ Si.routeOut('a1', Ti, setlevels, [c for c in [v.varname() for v in volumechans] if c.__contains__(Si.name[:-7])][0])
+ Ti.routeOut('a1', mixerout, setlevels)
+ Ti.routeIn('a1')
+ Ti.effect = True
+ orc.appendInstruments(Si, Ti)
+ orc.appendInstruments(mixerout)
+ orc.prependInstrument(setlevels)
+ return True
+ def commonChannels(self, chnName, default):
+ "returns a list of channel objects for INAMES"
+ return OrcBuilder.orcChannelMaker(INAMES, chnName, init = default)
+ def loadTracks(self, key, mode):
+ "see sc below"
+ self.scale = Tracks.Scale(key, mode)
+ midifiles = ResourceList(self.MidiFilePath, ".mid")
+ for m in midifiles:
+ score = Tracks.Midi2Score(self.MidiFilePath + "/" + m)
+ track = score.midiTrack2ScoreTrack(score.getTrack(0))
+ if m.startswith('Lead'):
+ self.leadtrack = track
+ elif m.startswith('Bass'):
+ self.basstrack = track
+ elif m.startswith('Chords'):
+ self.chordstrack = track
+ elif m.startswith('Drums'):
+ self.drumstrack = track
+ else:
+ raise ValueError, "Can't match MIDI file %s to instrument" %m
+
+def makePlayer(scene):
+ "create and return a player"
+ player = Tracks.beatDebugPlayer(scene.cs, scene.TimeQueue, scene.Params, scene.scale)
+ player.setBPM(scene.tempo)
+ lead = scene.orc.nameNumber['LeadSampler']
+ bass = scene.orc.nameNumber['BassSampler']
+ chords = scene.orc.nameNumber['ChordsSampler']
+ drums = scene.orc.nameNumber['DrumsSampler']
+ player.beatInstrumentMap(1, Lead = [scene.leadtrack, lead], Bass = [scene.basstrack,bass],
+ Chords = [scene.chordstrack, chords], Drums = [scene.drumstrack, drums])
+ return player
+
diff --git a/City/CsHelpers.py b/City/CsHelpers.py
new file mode 100755
index 0000000..800dae1
--- /dev/null
+++ b/City/CsHelpers.py
@@ -0,0 +1,194 @@
+#! /usr/bin/env python
+""" This file should be imported by all other files, so-as to defined cross-module global variables."""
+print "loading CsHelpers"
+import os,sys
+import pygame
+pygame.mixer.quit()
+
+platform = "undefined"
+
+try:
+ import olpcgames
+ import olpcgames.util
+ if olpcgames.ACTIVITY:
+ platform = "Sugar"
+except ImportError:
+ pass
+
+if platform == "undefined":
+ if sys.platform.startswith('darwin'):
+ platform = 'MacOSX'
+ elif sys.platform.startswith('win'):
+ platform = "Win32"
+ else: pass
+
+def modulePath():
+ "returns a string of the current path used for python modules"
+ if __name__ == '__main__':
+ return os.getcwd()
+ else: return os.path.dirname(os.path.abspath(__file__))
+
+ModulePath = modulePath()
+InstrumentPath = ModulePath + "/CsInstruments"
+ImagePath = ModulePath + "/Images"
+ScenePath = ModulePath + "/Scenes"
+
+PNAMES = ["Pitch", "Volume", "Timbre", "Tempo", "Length", "Density"]
+INAMES = ["Drums", "Lead", "Chords", "Bass"]
+SCENENAMES = [dir for dir in os.listdir(ScenePath) if os.path.isdir(ScenePath + '/' + dir)]
+
+class SceneData( object ):
+ def __init__(self, scene_name):
+ self.scene_name = scene_name
+ self.scene_path = ScenePath + "/" + scene_name
+ self.MidiFilePath = self.scene_path + "/MidiFiles"
+ self.AudioPath = self.scene_path + "/AudioFiles"
+ self.BassAudioPath = self.AudioPath + "/BassAudio"
+ self.LeadAudioPath = self.AudioPath + "/LeadAudio"
+ self.ChordsAudioPath = self.AudioPath + "/ChordsAudio"
+ self.DrumsAudioPath = self.AudioPath + "/DrumsAudio"
+
+class beatCluster( object ):
+ "An object which calculates a beat/time line, based on recorded beat time values"
+ def __init__(self, tempo_factor, beatlimit):
+ self.times = []
+ self.beat_id = 0
+ self.beatlimit = beatlimit
+ self.tempo_factor = tempo_factor
+ self.modulo_offset = 0
+ def beat2Time(self, beat):
+ "returns a time value based on a beat"
+ t = self._average_time()
+ bdiff = beat - self.beat_id
+ tdiff = bdiff * self.tempo_factor
+ result = t + tdiff
+ return result
+ def time2Beat(self, time):
+ "returns a beat value, when given a time"
+ t = self._average_time()
+ mtime = (time - self.modulo_offset) % (self.beat2Time(self.beatlimit) - self.modulo_offset) + self.modulo_offset
+ tdiff = mtime - t
+ bdiff = (tdiff * (1/self.tempo_factor))
+ b = self.beat_id + bdiff
+ return b % self.beatlimit
+ def _average_time(self):
+ return sum(self.times) / len(self.times)
+ def addTime(self, beat, time):
+ "store times"
+ if not self.times:
+ self.beat_id = beat
+ self.times.append(time)
+ self.modulo_offset = self.beat2Time(0)
+ else:
+ bdiff = beat - self.beat_id
+ tdiff = bdiff * self.tempo_factor
+ mtime = (time - self.modulo_offset) % (self.beat2Time(self.beatlimit) - self.modulo_offset) + self.modulo_offset
+ result = mtime - tdiff
+ self.times.append(result)
+ def __len__(self):
+ return len(self.times)
+
+
+
+class beatEstimator( object ):
+ "An object to collect beat and time values, and estimate when the next beat should be received"
+ def __init__(self, tempo_factor, tolerance, beatlimit):
+ self.beat_groups = []
+ self.tempo_factor = tempo_factor
+ self.tolerance = tolerance
+ self.beatlimit = beatlimit
+ def addBeat(self, beat, time):
+ "records the time a beat was received, and either adds it to an existing cluster or creates a new cluster, provide"
+ if not self.beat_groups:
+ bc = beatCluster(self.tempo_factor, self.beatlimit)
+ bc.addTime(beat, time)
+ self.beat_groups.append(bc)
+ else:
+ bt, bc = self.beat_match(time, True) # beatCluster.time2Beat(time). returns the expected beat of largest beatcluster, and the beatcluster itself
+ if abs(bt- beat) < self.tolerance: #where tolerance refers to a fractio of the beat.
+ print "adding to optimal beat cluster"
+ bc.addTime(beat, time)
+ else: #check the other beats
+ for b in filter(lambda xb: xb != bc, self.beat_groups):
+ otherbt = b.time2Beat(time)
+ if abs(otherbt - beat) < self.tolerance:
+ print "adding to other beat"
+ b.addTime(beat, time)
+ break
+ else:
+ print "creating new beat cluster"
+ # if no match, append a new beat
+ newbc = beatCluster(self.tempo_factor, self.beatlimit)
+ newbc.addTime(beat, time)
+ self.beat_groups.append(newbc)
+ def beat_match(self, time, include_reference = False):
+ "returns the expected time of the beat, referenced from the optimal beat"
+ optimal_bc = self.beat_groups[0]
+ for lb in self.beat_groups:
+ if len(lb) >= len(optimal_bc): optimal_bc = lb
+ if include_reference:
+ return (optimal_bc.time2Beat(time), optimal_bc)
+ else:
+ return optimal_bc.time2Beat(time)
+
+
+
+def getInstruments(*names):
+ "concatenate Instruments from the Instrument Path"
+ result = ""
+ for i in names:
+ Instrument = open(InstrumentPath + "/" + i, 'r')
+ result += Instrument.read() + '\n'
+ return result
+
+def limit(n, low, hi):
+ "set lower and upp bounds for values"
+ if n < low: return low
+ elif n > hi: return hi
+ else:
+ return n
+
+def rescale(n, oldmin, oldmax, newmin, newmax):
+ oldrange = float(oldmax) - oldmin
+ newrange = float(newmax) - newmin
+ oldsize = float(n) - oldmin
+ return (newrange / oldrange) * oldsize + newmin
+
+
+def ResourceList(path, suffix):
+ "returns a list of files in the directory"
+ return [f for f in os.listdir(path) if f[-len(suffix):] == suffix and f[0] != '.']
+
+
+def removeillegals(s):
+ "removes illegal csound characters from names"
+ result = ''
+ for i in s:
+ if i.isalnum() or i == '_':
+ result += i
+ return result
+
+
+def aselect(lst, *indices):
+ result = []
+ for i in range(len(lst)):
+ if i not in indices:
+ result.append(lst[i])
+ return result
+
+def select(lst, *indices):
+ result = []
+ for i in range(len(lst)):
+ if i in indices:
+ result.append(lst[i])
+ return result
+
+
+
+if __name__ == '__main__':
+ print ResourceList(InstrumentPath, ".orc")
+ print ResourceList(MidiFilePath, ".mid")
+
+
+
+
diff --git a/City/CsSched.py b/City/CsSched.py
new file mode 100755
index 0000000..14e5588
--- /dev/null
+++ b/City/CsSched.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python
+
+import csnd, heapq
+from CsHelpers import *
+
+class Csound:
+ "precompiles a csound object"
+ def __init__(self):
+ self.csound = csnd.CppSound()
+ self.csound.setPythonMessageCallback()
+ self.csound.PreCompile()
+ def __repr__(self):
+ return "Precompiled Csound object"
+
+
+class Sched:
+ def __init__(self):
+ self.queue = []
+ def schedEvent(self, time, func, *args):
+ heapq.heappush(self.queue, (time, func, args))
+ def getTime(self):
+ if self:
+ return self.queue[0][0]
+ else:
+ return False
+ def getFunc(self):
+ if len(self) == 0:
+ return False
+ else:
+ return self.queue[0][1]
+ def getArgs(self):
+ if len(self) == 0:
+ return False
+ else:
+ return self.queue[0][2]
+ def __len__(self):
+ return len(self.queue)
+ def __repr__(self):
+ return str(self.queue)
+
+class CsoundChan:
+ "a container for Csound channel data"
+ #type is either Audio, Control, or String
+ #Dirction = INput or Output
+ #subType = interger, linear or exponential
+ #default = default value
+ #minval = suggested minimum
+ #maxval == suggested maximum
+ pass
+
+
+
+def channels(csound):
+ "returns a list of Input and output software bus channels"
+ Chanlist = csnd.CsoundChannelList(csound)
+ result = []
+ for ndx in range(Chanlist.Count()):
+ ch = CsoundChan()
+ ch.name = Chanlist.Name(ndx)
+ if Chanlist.IsAudioChannel(ndx):
+ ch.type = "Audio"
+ elif Chanlist.IsControlChannel(ndx):
+ ch.type = "Control"
+ elif Chanlist.IsStringChannel(ndx):
+ ch.type = "String"
+ else: pass
+ if Chanlist.IsInputChannel(ndx) and Chanlist.IsOutputChannel(ndx):
+ ch.direction = "bidirectional"
+ elif Chanlist.IsInputChannel(ndx):
+ ch.direction = "input"
+ elif Chanlist.IsOutputChannel(ndx):
+ ch.direction = "output"
+ else: pass
+ if Chanlist.SubType(ndx) > 0:
+ tmp = ['integer', 'linear', 'exponential']
+ ch.subtype = (tmp[Chanlist.SubType(ndx) - 1])
+ ch.default = (Chanlist.DefaultValue(ndx))
+ ch.minval = (Chanlist.MinValue(ndx))
+ ch.maxval = (Chanlist.MaxValue(ndx))
+ result.append(ch)
+ Chanlist.Clear()
+ return result
+
+
+
+class CsoundPerformer:
+ def pollScheduler(self, schedObj):
+ st = schedObj.getTime()
+ if st:
+ t = self.perfTime()
+ if t >= st:
+ obj = heapq.heappop(schedObj.queue)
+ (obj[1] (*obj[2]))
+ def __init__(self, schedObj, orcObj, *cs):
+ "SchedObj is a Csound timer instance, orcObJ is a CsOrcConstructor Object"
+ self.Timer = csnd.CsoundTimer()
+ self.schedObj = schedObj
+ self.orcObj = orcObj
+ if len(cs) == 0:
+ cs = Csound()
+ self.csound = Csound.csound
+ else: self.csound = cs[0]
+ self.csound.setOrchestra(orcObj.exportOrc())
+ self.csound.setScore(orcObj.sco)
+ self.time = 0
+ if platform == "Sugar":
+ self.csound.setCommand("csound -b256 -B2048 -+rtaudio=alsa -odac --expression-opt --sched=1 -d -m0 /tmp/tmp.orc /tmp/tmp.sco")
+ else:
+ self.csound.setCommand("csound -b256 -B2048 -odac --expression-opt -d -m0 /tmp/tmp.orc /tmp/tmp.sco")
+ self.csound.exportForPerformance()
+ self.csound.compile()
+ self.Channels = channels(self.csound)
+ self.perf = csnd.CsoundPerformanceThread(self.csound)
+ self.perf.Play()
+ self.perf.SetProcessCallback(self.pollScheduler, schedObj)
+ def perfTime(self):
+ return self.Timer.GetRealTime()
+ def Stop(self):
+ self.perf.Stop()
+ #self.perf.Join()
+ self.csound.Cleanup()
+ def setChannelValue(self, channame, value):
+ self.csound.SetChannel(channame, value)
+ def getChannelValue(self, channame):
+ return self.csound.GetChannel(channame)
+ def getChannelList(self):
+ return csnd.CsoundChannelList(self.csound)
+ def getChannelNames(self):
+ chlst = self.getChannelList()
+ return [chlst.Name(ch) for ch in range(chlst.Count())]
+ def playParams(self, ins, start, dur, *params):
+ "send score message to Csound with parameter values"
+ if start < 0: start = 0
+ s = ' '.join(map(str, (['i', ins, start, dur] + [str(n) for n in params])))
+ self.perf.InputMessage(s)
+ def playNote(self, ins, note):
+ "send score message to Csound using a note object"
+ start = note[0]
+ dur = note[1]
+ params = note[2:]
+ self.perf.InputMessage(' '.join(['i', str(ins),str(start),str(dur)]+[str(n) for n in params]))
+
+
diff --git a/City/Images/Bass.png b/City/Images/Bass.png
new file mode 100755
index 0000000..31507d9
--- /dev/null
+++ b/City/Images/Bass.png
Binary files differ
diff --git a/City/Images/Chords.png b/City/Images/Chords.png
new file mode 100755
index 0000000..f4d4660
--- /dev/null
+++ b/City/Images/Chords.png
Binary files differ
diff --git a/City/Images/Drums.png b/City/Images/Drums.png
new file mode 100755
index 0000000..7a8ee0a
--- /dev/null
+++ b/City/Images/Drums.png
Binary files differ
diff --git a/City/Images/Lead.png b/City/Images/Lead.png
new file mode 100755
index 0000000..6ebc70a
--- /dev/null
+++ b/City/Images/Lead.png
Binary files differ
diff --git a/City/Images/Mic.png b/City/Images/Mic.png
new file mode 100755
index 0000000..34fa1bd
--- /dev/null
+++ b/City/Images/Mic.png
Binary files differ
diff --git a/City/Images/Video.png b/City/Images/Video.png
new file mode 100755
index 0000000..c5c25ca
--- /dev/null
+++ b/City/Images/Video.png
Binary files differ
diff --git a/City/Images/camera-busy.svg b/City/Images/camera-busy.svg
new file mode 100755
index 0000000..6c79d17
--- /dev/null
+++ b/City/Images/camera-busy.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#ffffff">
+ <!ENTITY fill_color "#000000">
+]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="camera-external">
+ <g display="inline">
+ <g>
+ <polygon fill="&fill_color;" points="38.532,14.52 34.904,9.862 18.783,9.862 15.155,14.52 6.29,14.52 6.29,38.973 48.209,38.973 48.209,14.52 "/>
+ </g>
+ <g>
+ <polygon fill="none" points="38.532,14.52 34.904,9.862 18.783,9.862 15.155,14.52 6.29,14.52 6.29,38.973 48.209,38.973 48.209,14.52 " stroke="&stroke_color;" stroke-width="3.5"/>
+ </g>
+ </g>
+ <path d="M20.601,26.441c0,3.67,2.979,6.648,6.65,6.648 c3.667,0,6.646-2.979,6.646-6.648c0-3.668-2.979-6.652-6.646-6.652C23.581,19.789,20.601,22.775,20.601,26.441z" display="inline" fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5"/>
+ <rect display="inline" fill="&stroke_color;" height="4.193" width="6.287" x="38.099" y="18.418"/>
+</g></svg> \ No newline at end of file
diff --git a/City/Images/camera-external.svg b/City/Images/camera-external.svg
new file mode 100755
index 0000000..54d9c86
--- /dev/null
+++ b/City/Images/camera-external.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#ffffff">
+]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="camera-external">
+ <g display="inline">
+ <g>
+ <polygon fill="&fill_color;" points="38.532,14.52 34.904,9.862 18.783,9.862 15.155,14.52 6.29,14.52 6.29,38.973 48.209,38.973 48.209,14.52 "/>
+ </g>
+ <g>
+ <polygon fill="none" points="38.532,14.52 34.904,9.862 18.783,9.862 15.155,14.52 6.29,14.52 6.29,38.973 48.209,38.973 48.209,14.52 " stroke="&stroke_color;" stroke-width="3.5"/>
+ </g>
+ </g>
+ <path d="M20.601,26.441c0,3.67,2.979,6.648,6.65,6.648 c3.667,0,6.646-2.979,6.646-6.648c0-3.668-2.979-6.652-6.646-6.652C23.581,19.789,20.601,22.775,20.601,26.441z" display="inline" fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5"/>
+ <rect display="inline" fill="&stroke_color;" height="4.193" width="6.287" x="38.099" y="18.418"/>
+</g></svg> \ No newline at end of file
diff --git a/City/Images/jam2jamXO_2.png b/City/Images/jam2jamXO_2.png
new file mode 100644
index 0000000..893b4c2
--- /dev/null
+++ b/City/Images/jam2jamXO_2.png
Binary files differ
diff --git a/City/Images/media-playback-pause.svg b/City/Images/media-playback-pause.svg
new file mode 100755
index 0000000..aa4a585
--- /dev/null
+++ b/City/Images/media-playback-pause.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#FFFFFF">
+]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="media-playback-pause">
+ <g display="inline">
+ <path d="M27.499,0C12.311,0,0,12.313,0,27.5C0,42.688,12.311,55,27.499,55C42.686,55,55,42.688,55,27.5 C55,12.313,42.686,0,27.499,0z M27.497,52.646c-13.826,0-25.033-11.209-25.035-25.033C2.463,13.787,13.67,2.577,27.497,2.574 c13.826,0.003,25.035,11.213,25.039,25.038C52.531,41.437,41.322,52.646,27.497,52.646z" fill="&fill_color;"/>
+ <path d="M27.496,5C15.068,5,4.997,15.076,4.997,27.5c0,12.427,10.071,22.5,22.499,22.5 c12.428,0,22.5-10.073,22.5-22.5C49.996,15.076,39.924,5,27.496,5z M25.938,35.443h-5.801V19.741h5.801V35.443z M34.855,35.443 h-5.803V19.741h5.803V35.443z" fill="&fill_color;"/>
+ </g>
+</g></svg> \ No newline at end of file
diff --git a/City/Images/media-playback-start.svg b/City/Images/media-playback-start.svg
new file mode 100755
index 0000000..e2e0051
--- /dev/null
+++ b/City/Images/media-playback-start.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#FFFFFF">
+]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="media-playback-start">
+ <g display="inline">
+ <g>
+ <g>
+ <g>
+ <path d="M27.496,5.051C15.036,5.07,4.952,15.155,4.93,27.618C4.952,40.08,15.036,50.164,27.496,50.185 c12.465-0.021,22.549-10.104,22.568-22.566C50.045,15.155,39.961,5.07,27.496,5.051z M21.551,37.977v-20.31l17.961,10.155 L21.551,37.977z" fill="&fill_color;"/>
+ </g>
+ </g>
+ <path d="M27.498,0C12.311,0,0,12.313,0,27.5C0,42.688,12.311,55,27.498,55C42.686,55,55,42.688,55,27.5 C55,12.313,42.686,0,27.498,0z M27.496,52.646c-13.826,0-25.033-11.209-25.035-25.033C2.463,13.787,13.67,2.577,27.496,2.574 c13.826,0.003,25.035,11.213,25.039,25.038C52.531,41.437,41.322,52.646,27.496,52.646z" fill="&fill_color;"/>
+ </g>
+ </g>
+</g></svg> \ No newline at end of file
diff --git a/City/OrcBuilder.py b/City/OrcBuilder.py
new file mode 100755
index 0000000..776cc9e
--- /dev/null
+++ b/City/OrcBuilder.py
@@ -0,0 +1,342 @@
+#!/usr/bin/env python
+
+import csnd, sys
+from CsHelpers import *
+
+
+class Ftable:
+ def __init__(self):
+ self.start = 0
+ self.num = 0
+ self.size = 8192
+ self.gen = 10
+ self.name = "sine"
+ self.args = [10,1]
+ def varname(self):
+ return "gi_"+self.name
+ def __repr__(self):
+ head = [self.varname(), 'ftgen', str(self.num)+', ']
+ args = [str(self.start), str(self.size), str(self.gen)] + map(str, self.args)
+ return ' '.join(head) + ', '.join(args)
+
+class FtableBreakPoint(Ftable):
+ "constructs a Gen 2 ftable"
+ def __init__(self, name, *args, **num):
+ Ftable.__init__(self)
+ self.name = name
+ self.gen = -2
+ self.size = (2**i for i in xrange(2,100) if 2**i >= len(args)).next()
+ self.args = args
+ if num.has_key('num'): self.num = num['num']
+
+class FtableSample(Ftable):
+ "loads samples into GEN 1, and optional basepitch storage"
+ def __init__(self, pathname, *basepit, **num):
+ Ftable.__init__(self)
+ self.gen = -1
+ filename = pathname.rpartition('/')[-1]
+ ndx = filename.find('.')
+ if ndx != -1:
+ self.name = removeillegals(filename[0:ndx])
+ else: self.name = removeillegals(filename)
+ self.size = 0
+ self.args = ['"'+pathname+'"', 0, 0, 0]
+ if len(basepit) > 0:
+ self.basePitch = basepit[0]
+ if num.has_key('num'): self.num = num['num']
+
+class FtableLookup(Ftable):
+ def __init__(self, name, skew, *sampletables, **num):
+ "uses GEN17 to make an x,y lookup table suitable for mid pitch lookup. GEN17"
+ #skew calculates the point at which the next table is lookup up.
+ #pitnamedict is a dictionary of {basepitches:samplenames,...}
+ Ftable.__init__(self)
+ pitnamedict = {}
+ for s in sampletables:
+ pitnamedict[s.basePitch] = s.varname()
+ sorted = []
+ for key in pitnamedict:
+ sorted.append([key, pitnamedict[key]])
+ sorted.sort()
+ args = []
+ for i in range(len(sorted)):
+ if i == 0:
+ args.append(i)
+ args.append(sorted[i][1])
+ else:
+ args.append(int(round (sorted[i][0] - (sorted[i][0] - sorted[i-1][0]) * skew)))
+ args.append(sorted[i][1])
+ self.gen = -17
+ self.name = name
+ self.size = 128
+ self.args = args
+ if num.has_key('num'): self.num = num['num']
+
+class OrcChan:
+ def __init__(self, name, direction, rate, init):
+ self.name = name
+ self.direction = direction
+ self.rate = rate
+ self.initval = init
+ def mode(self):
+ return (1 if self.direction == 'input' else (2 if self.direction == 'output' else 3))
+ def varname(self):
+ return 'g'+('a' if self.rate == 'audio' else ('k' if self.rate == 'control' else ('S' if self.rate == 'string' else 'i')))+'_'+self.name
+ def initline(self):
+ return self.varname() + '\t' + 'init' + '\t' + str(self.initval)
+ def __repr__(self):
+ final = self.varname() + ' '+'chnexport'+' \"'+self.name+'\", '+str(self.mode())+('\n'+self.initline() if (self.rate == 'audio' or self.rate == 'string') else ',2,1,0,1'+ '\n'+self.initline())
+ return final
+
+def orcChannelMaker(insnames, parameter, direction = "input", rate = "control", init = 1):
+ "a simple function to generate numerous channels of the same type"
+ result = []
+ for i in insnames:
+ name = i+parameter
+ o = OrcChan(name, direction, rate, init)
+ result.append(o)
+ return result
+
+#Not cognisant of scoreline parameter variables at this stage.
+class OrcInstrument:
+ def __init__(self, name='undefined'):
+ self.name = name
+ self.lines = []
+ self.effect = False #if true, then an 'always on' scoreline is automatically added.
+ def header(self):
+ return ["instr \t$"+self.name+"\n"]
+ def varname(self):
+ return "$"+self.name
+ def insertLine(self, line):
+ self.lines.append(line)
+ def routeOut(self, asendvar, outINS, SetLvlIns, gkvarname = 1, *chan):
+ "SO, gkvarname is 1 by default, but should be the gkvariable if going to mixout"
+ SetLvlIns.setLevel(self, outINS, gkvarname)
+ self.insertLine("\tMixerSend "+asendvar+", p1, "+outINS.varname()+", "+str((chan[0] if len(chan)>0 else 0)))
+ def routeIn(self, ainvar, *chan):
+ self.lines.insert(0, ainvar+" MixerReceive "+"p1,"+str((chan[0] if len(chan)>0 else 0)))
+ def __repr__(self):
+ result = self.header() + self.lines
+ return "\n".join(result) + '\n\nendin\n'
+
+class OrcSetLevelInstrument(OrcInstrument):
+ def __init__(self):
+ OrcInstrument.__init__(self)
+ self.name = "mixerSetLevels"
+ self.routemap = {}
+ def setLevel(self, sendINS, bussINS, gkvarname):
+ self.routemap[sendINS.name] = bussINS.name
+ self.insertLine("\t MixerSetLevel "+sendINS.varname()+", "+bussINS.varname()+", "+str(gkvarname)+"\n") #change this to the gkvariable name
+ def mixout(self, ainvar, *chan):
+ "sends output of an instrument to a mixer buss"
+ self.insertLine("\tMixerSend "+ainvar+", p1, "+"$output, "+str((chan[0] if len(chan)>0 else 0)))
+
+
+class OrcMixoutInstrument(OrcInstrument):
+ def __init__(self):
+ OrcInstrument.__init__(self)
+ self.name = "output"
+ self.routeIn("am")
+ self.insertLine("""
+ am eqfil am, 900, 200, 0.2
+ a3 nreverb am, 0.12, 1, 0, 8, gi_rvbc1, 4, gi_rvba1
+ a4 nreverb am, 0.12, 1, 0, 8, gi_rvbc2, 4, gi_rvba2
+ a3 = am + a3*0.23
+ a4 = am + a4*0.23
+ outs a3, a4
+ ;outs am, am
+ MixerClear
+ """)
+
+
+
+class OrcConstructor:
+ def __init__(self):
+ self.orc = ""
+ self.sco = "f0 28800 \n"
+ self.sr = 44100
+ self.ksmps = 100
+ self.nchnls = 2
+ self.dbfs = 1
+ self.macros = []
+ self.ftabs = []
+ self.chans = []
+ self.instruments = []
+ self.csline = []
+ self.nameNumber = {}
+ self.insertftables(Ftable()) #insert a sine by default
+ def insertLines(self, lines):
+ self.csline.append(lines)
+ def insertInsnums(self, Instruments):
+ insnumgen = (i for i in xrange(1, 100))
+ for i in Instruments:
+ num = insnumgen.next()
+ self.nameNumber[i.name] = num
+ self.macros.append("#define "+i.name+" #"+str(num)+"#")
+ if i.effect:
+ self.sco = self.sco + "i%s 0 -1 \n" %num
+ def insertftables(self, *tabs):
+ "inserts Orcfunction tables using Ftable objects"
+ for t in tabs:
+ self.ftabs.append(t)
+ def insertChannels(self, *OrcChans):
+ "inserts a Orcchannel objects into a Csound orc"
+ for c in OrcChans:
+ self.chans.append(c)
+ def appendInstruments(self, *CsIns):
+ for i in CsIns:
+ self.instruments.append(i)
+ def prependInstrument(self, CsIns):
+ "inserts an instrument at the front of the orchestra"
+ self.instruments.insert(0, CsIns)
+ def exportOrc(self):
+ self.insertInsnums(self.instruments)
+ header = [x+y for x,y in zip(['sr = ', 'ksmps = ', 'nchnls = ', '0dbfs = '], map(str,[self.sr, self.ksmps, self.nchnls, self.dbfs]))]
+ insnums = self.macros
+ ftabs = map(str, self.ftabs)
+ lines = map(str, self.csline)
+ chans = map(str, self.chans)
+ Ins = map(str, self.instruments)
+ result = header + insnums + ftabs + chans + lines + Ins
+ return '\n'.join(result)
+ def __repr__(self):
+ return "CSound orchestra object" + str(self.__dict__)
+
+class sndInfo:
+ def __init__(self, path, *csd):
+ "query information of an audio file at path. csd is a precompiled csound instance"
+ if len(csd) == 0:
+ cs = Csound()
+ self.cs = Csound.csound
+ else: self.cs = csd[0] #can pass a precompiled csound as an argument
+ self.contents = ''
+ args = csnd.CsoundArgVList()
+ args.Append('sndinfo')
+ args.Append('-i')
+ args.Append(path)
+ old_stdout = sys.stdout
+ sys.stdout = self
+ err = self.cs.RunUtility('sndinfo', args.argc(), args.argv())
+ sys.stdout = old_stdout
+ self._lines()
+ header = [l for l in self.lines if l.__contains__('\tsrate')]
+ self.header = header[0].split(',')
+ def write(self, c):
+ self.contents += c
+ def _lines(self):
+ self.lines = self.contents.splitlines()
+ def sr(self):
+ srl = [c for c in self.header[0] if c.isdigit()]
+ return int(''.join(srl))
+ def chans(self):
+ if self.header[1].__contains__('monaural'):
+ return 1
+ else: return 2
+ def type(self):
+ return self.header[2]
+ def duration(self):
+ s = self.header[3]
+ return float(''.join([n for n in s if n.isdigit() or n == '.']))
+ def findNoteAttribute(self, attr):
+ result = 0
+ for i in self.lines:
+ if i.startswith(attr, 2):
+ result = int(''.join([n for n in i if n.isdigit()]))
+ return result
+ def BaseNote(self):
+ "return the Base Note"
+ return self.findNoteAttribute('Base')
+
+def orcLoadSamples(orc, Cs, fnlookupname, *paths):
+ "Inserts Gen1 ftables for samples located in paths, and an associated GEN17 midi pitch lookup table based on base pitch in soundfile into orc"
+ sfns = []
+ for p in paths:
+ for f in ResourceList(p,'.aif'):
+ snd = sndInfo(p+'/'+f, Cs.csound)
+ bn = snd.BaseNote()
+ fn = FtableSample(p+'/'+f, bn)
+ orc.insertftables(fn)
+ sfns.append(fn)
+ flookup = FtableLookup(fnlookupname, 0.3, *sfns)
+ orc.insertftables(flookup)
+ return flookup
+
+if __name__ == '__main__':
+ print "running OrcBuilder as __main__"
+
+ from CsSched import *
+ Csynth = Csound()
+ TimeQueue = Sched()
+
+ #Create a csound orchestra
+ orc = OrcConstructor()
+ orc.sr = 22050
+ orc.ksmps = 256
+
+ #Function tables
+ lookuptabs = [orcLoadSamples(orc, Csynth, instr+"Lookup", eval(instr+"AudioPath")) for instr in INAMES]
+ ftabs = [FtableBreakPoint("rvbc1", -558, -594, -638, -678, -711, -745, -778, -808, 0.8, 0.79, 0.78, 0.77, 0.76, 0.75, 0.74, 0.73),
+ FtableBreakPoint("rvbc2", -517, -540, -656, -699, -752, -799, -818, -841, 0.8, 0.79, 0.78, 0.77, 0.76, 0.75, 0.74, 0.73),
+ FtableBreakPoint("rvba1", -278, -220, -170, -122, 0.4, 0.52, 0.64, 0.76),
+ FtableBreakPoint("rvba2", -333, -263, -166, -105, 0.5, 0.52, 0.64, 0.76)]
+ orc.insertftables(*ftabs)
+
+ #Control Channels: Should conform to the Parameter naming convention already in use.
+ volumechans = orcChannelMaker(INAMES, "Volume")
+ timbrechans = orcChannelMaker(INAMES, "Timbre", init = 0.5)
+ orc.insertChannels(*volumechans)
+ orc.insertChannels(*timbrechans)
+ #timbrechans[0].varname()
+
+ #Instruments
+ #first, establish a setlevel instrument
+ setlevels = OrcSetLevelInstrument()
+ setlevels.effect = True
+ #then work backwards.
+ #A mixer:
+ mixerout = OrcMixoutInstrument()
+ mixerout.effect = True
+
+ #sampler instruments
+ samplerbody = """
+ idur = p3
+ iamp = p4 * (0dbfs / 127)
+ kcps init cpsmidinn(p5)
+ isamp table p5, %s
+ a1 loscil iamp, kcps, isamp
+ a1 dcblock a1
+ kdeclick linseg 0, 0.001, 1, idur - 0.03 - 0.001, 1, 0.03, 0
+ a1 = a1 * kdeclick
+ """
+ #timbre instruments
+ #be aware that samplerates lower than 22050 tend to blow up the rezzy filter
+ #at this level of resonance.
+ timbrebody = ("""
+ idur = p3
+ ires = 4.75
+ kfco expcurve %s, 14
+ kfco = kfco * """ + str(orc.sr*0.5 * 0.65 + 200) + """
+
+ a1 rezzy a1, kfco, ires
+ """)
+
+ SamplerInstruments, TimbreInstruments = [OrcInstrument(i+'Sampler') for i in INAMES], [OrcInstrument(i+'Timbre') for i in INAMES]
+
+ #it would be nice to abstract this one, but it's quite complex. Maybe later.
+ for Si, Ti in zip(SamplerInstruments, TimbreInstruments):
+ Si.insertLine(samplerbody % [x for x in [n.varname() for n in lookuptabs] if x.__contains__(Si.name[:-7])][0])
+ Ti.insertLine(timbrebody % [y for y in [j.varname() for j in timbrechans] if y.__contains__(Ti.name[:-6])][0])
+ Si.routeOut('a1', Ti, setlevels)
+ Ti.routeOut('a1', mixerout, setlevels)
+ Ti.routeIn('a1')
+ Ti.effect = True
+ orc.appendInstruments(Si, Ti)
+
+
+ #finally, add the mixer and level instruments
+ orc.appendInstruments(mixerout)
+ orc.prependInstrument(setlevels)
+ #get numbers from names:
+ #orc.nameNumber['BassSampler']
+ print orc.exportOrc()
+
diff --git a/City/Parameters.py b/City/Parameters.py
new file mode 100755
index 0000000..000405e
--- /dev/null
+++ b/City/Parameters.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python
+"""Describes an object for storing parameter values.
+All Controllers should write to this object.
+All Readers should read from the object"""
+
+import random
+from CsHelpers import * #import global names and functions
+
+class Instrument():
+ def __init__(self, name, x = 0, y = 0, w = 0, h = 0):
+ self.name = name
+ self.lastRect = pygame.Rect(x,y,w,h)
+ self.Rect = pygame.Rect(x,y,w,h)
+ self.image = None
+ self.active_image = None
+ self.inactive_image = None
+ self.Touch = False
+ self.active = False
+ def loadImage(self, path, *scale):
+ img = pygame.image.load(path)
+ if scale:
+ img = pygame.transform.scale(img, (img.get_width() * scale[0], img.get_height() * scale[-1]))
+ img = img.convert_alpha()
+ self.active_image = img
+ self.Rect.size = img.get_size()
+ self.lastRect.size = img.get_size()
+ imgcopy = self.active_image.copy()
+ imgcopy.fill((255,5,55) , None, pygame.BLEND_MIN)
+ imgcopy.fill((75,75,75) , None, pygame.BLEND_ADD)
+ self.inactive_image = imgcopy
+ self.deactivate()
+ def activate(self):
+ self.image = self.active_image
+ self.active = True
+ self.Touch = True
+ def deactivate(self):
+ self.image = self.inactive_image
+ self.active = False
+ self.Touch = True
+ def x(self):
+ return self.Rect[0]
+ def y(self):
+ return self.Rect[1]
+ def lastCtr(self):
+ return self.lastRect.center
+ def ctr(self):
+ return self.Rect.center
+ def __setattr__(self, attr, value):
+ if attr == 'x':
+ self.lastRect[0] = self.Rect[0]
+ self.Rect[0] = value
+ self.Touch = True
+ elif attr == 'y':
+ self.lastRect[1] = self.Rect[1]
+ self.Rect[1] = value
+ self.Touch = True
+ elif attr == 'ctr':
+ self.lastRect[0] = self.Rect[0]
+ self.lastRect[1] = self.Rect[1]
+ self.Rect.center = value
+ self.Touch = True
+ else:
+ self.__dict__[attr] = value
+ def __repr__(self):
+ return ''.join(map(str, [self.name, " Instrument at location ", (self.x(), self.y())]))
+
+
+class Parameter():
+ def __init__(self, name):
+ self.name = name
+ self.Instrumentvalues = [[Instrument(ins), 0.5] for ins in INAMES]
+ self.state = False
+ self.active = False
+ self.correspondant = None
+ def getValue(self, insname):
+ for ndx in range(len(self.Instrumentvalues)):
+ if self.Instrumentvalues[ndx][0].name == insname:
+ return self.Instrumentvalues[ndx][1]
+ def setValue(self, ins, val):
+ ndx = [i[0].name for i in self.Instrumentvalues].index(ins)
+ self.Instrumentvalues[ndx][1] = val
+ def __repr__(self):
+ return self.name
+
+class Perimeter():
+ def __init__(self):
+ self.data = {}
+ for n in PNAMES:
+ self.data[n] = Parameter(n)
+ csoundChannels = []
+ def csoundChannels(self, cs):
+ self.csound = cs
+ self.csoundChannels = cs.Channels
+ def getValue(self, name, instrument):
+ return self.data[name].getValue(instrument)
+ def setValue(self, name, instrument, value):
+ if name == 'Tempo':
+ for i in self.data['Tempo'].Instrumentvalues:
+ i[1] = value
+ else:
+ self.data[name].setValue(instrument, value)
+ namelen = len(name)
+ if len(self.csoundChannels) > 0:
+ for ch in self.csoundChannels:
+ param = ch.name[-namelen:]
+ if param == name:
+ ins = ch.name[:-len(param)]
+ if (ins == instrument and
+ (ch.direction == "input" or "bidirectional") and
+ ch.type == "Control"):
+ self.csound.setChannelValue(ch.name, value)
+ def getPlist(self):
+ "returns a list of all parameters"
+ return self.data.values()
+ def getPdict(self):
+ return self.data
+
+
+if __name__ == '__main__':
+ P = Perimeter()
+ print "Value for Drum density is :", P.getValue('Density', 'Drums')
+ print "setting new value"
+ P.setValue('Density', 'Drums', 0.75)
+ print "Value for Drum density is :", P.getValue('Density', 'Drums')
+ c = check(P)
+ c.setp(0.9)
+ c.getp()
+
diff --git a/City/Scenes/Blues/AudioFiles/BassAudio/BassC2[41-48-50].aif b/City/Scenes/Blues/AudioFiles/BassAudio/BassC2[41-48-50].aif
new file mode 100644
index 0000000..3e3fd98
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/BassAudio/BassC2[41-48-50].aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/BassAudio/BassC3[59-60-62].aif b/City/Scenes/Blues/AudioFiles/BassAudio/BassC3[59-60-62].aif
new file mode 100644
index 0000000..bd3ab7f
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/BassAudio/BassC3[59-60-62].aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/BassAudio/BassC4[70-72-78].aif b/City/Scenes/Blues/AudioFiles/BassAudio/BassC4[70-72-78].aif
new file mode 100644
index 0000000..f9ec61b
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/BassAudio/BassC4[70-72-78].aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/BassAudio/BassF#2[51-54-58].aif b/City/Scenes/Blues/AudioFiles/BassAudio/BassF#2[51-54-58].aif
new file mode 100644
index 0000000..c099d56
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/BassAudio/BassF#2[51-54-58].aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/ChordsAudio/epnoC4[70-72-73].aif b/City/Scenes/Blues/AudioFiles/ChordsAudio/epnoC4[70-72-73].aif
new file mode 100644
index 0000000..475ac71
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/ChordsAudio/epnoC4[70-72-73].aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/ChordsAudio/epnoC5[82-84-90].aif b/City/Scenes/Blues/AudioFiles/ChordsAudio/epnoC5[82-84-90].aif
new file mode 100644
index 0000000..f89023c
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/ChordsAudio/epnoC5[82-84-90].aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/ChordsAudio/epnoE3[63-64-65].aif b/City/Scenes/Blues/AudioFiles/ChordsAudio/epnoE3[63-64-65].aif
new file mode 100644
index 0000000..fd1a6c5
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/ChordsAudio/epnoE3[63-64-65].aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/ChordsAudio/epnoE4[74-76-77].aif b/City/Scenes/Blues/AudioFiles/ChordsAudio/epnoE4[74-76-77].aif
new file mode 100644
index 0000000..b4d5964
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/ChordsAudio/epnoE4[74-76-77].aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/DrumsAudio/C1_kick_drum.aif b/City/Scenes/Blues/AudioFiles/DrumsAudio/C1_kick_drum.aif
new file mode 100644
index 0000000..cedbae3
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/DrumsAudio/C1_kick_drum.aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/DrumsAudio/D1_snare_drum.aif b/City/Scenes/Blues/AudioFiles/DrumsAudio/D1_snare_drum.aif
new file mode 100644
index 0000000..4ff68d0
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/DrumsAudio/D1_snare_drum.aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/DrumsAudio/F#1_closed_hi_hat.aif b/City/Scenes/Blues/AudioFiles/DrumsAudio/F#1_closed_hi_hat.aif
new file mode 100644
index 0000000..ef60aac
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/DrumsAudio/F#1_closed_hi_hat.aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/DrumsAudio/Tamborine 22 16 Mono.aif b/City/Scenes/Blues/AudioFiles/DrumsAudio/Tamborine 22 16 Mono.aif
new file mode 100644
index 0000000..6b6f85f
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/DrumsAudio/Tamborine 22 16 Mono.aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/LeadAudio/B2_guitar.aif b/City/Scenes/Blues/AudioFiles/LeadAudio/B2_guitar.aif
new file mode 100644
index 0000000..9a06c2f
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/LeadAudio/B2_guitar.aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/LeadAudio/B3_guitar.aif b/City/Scenes/Blues/AudioFiles/LeadAudio/B3_guitar.aif
new file mode 100644
index 0000000..a229b76
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/LeadAudio/B3_guitar.aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/LeadAudio/E2_guitar.aif b/City/Scenes/Blues/AudioFiles/LeadAudio/E2_guitar.aif
new file mode 100644
index 0000000..0df7f1e
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/LeadAudio/E2_guitar.aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/LeadAudio/E3_guitar.aif b/City/Scenes/Blues/AudioFiles/LeadAudio/E3_guitar.aif
new file mode 100644
index 0000000..e8f5be5
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/LeadAudio/E3_guitar.aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/LeadAudio/E4_guitar.aif b/City/Scenes/Blues/AudioFiles/LeadAudio/E4_guitar.aif
new file mode 100644
index 0000000..9909889
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/LeadAudio/E4_guitar.aif
Binary files differ
diff --git a/City/Scenes/Blues/AudioFiles/LeadAudio/E5_guitar.aif b/City/Scenes/Blues/AudioFiles/LeadAudio/E5_guitar.aif
new file mode 100644
index 0000000..f1f7076
--- /dev/null
+++ b/City/Scenes/Blues/AudioFiles/LeadAudio/E5_guitar.aif
Binary files differ
diff --git a/City/Scenes/Blues/Blues_Data.txt b/City/Scenes/Blues/Blues_Data.txt
new file mode 100644
index 0000000..6542e85
--- /dev/null
+++ b/City/Scenes/Blues/Blues_Data.txt
@@ -0,0 +1,4 @@
+Tempo = 65
+Key = E
+Mode = minor
+
diff --git a/City/Scenes/Blues/MidiFiles/Bass.mid b/City/Scenes/Blues/MidiFiles/Bass.mid
new file mode 100644
index 0000000..16967a5
--- /dev/null
+++ b/City/Scenes/Blues/MidiFiles/Bass.mid
Binary files differ
diff --git a/City/Scenes/Blues/MidiFiles/Chords.mid b/City/Scenes/Blues/MidiFiles/Chords.mid
new file mode 100644
index 0000000..139be9d
--- /dev/null
+++ b/City/Scenes/Blues/MidiFiles/Chords.mid
Binary files differ
diff --git a/City/Scenes/Blues/MidiFiles/Drums.mid b/City/Scenes/Blues/MidiFiles/Drums.mid
new file mode 100644
index 0000000..576167d
--- /dev/null
+++ b/City/Scenes/Blues/MidiFiles/Drums.mid
Binary files differ
diff --git a/City/Scenes/Blues/MidiFiles/Lead.mid b/City/Scenes/Blues/MidiFiles/Lead.mid
new file mode 100644
index 0000000..b7636d6
--- /dev/null
+++ b/City/Scenes/Blues/MidiFiles/Lead.mid
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/BassAudio/Dance1Bass[28-36-39]mn_000.aif b/City/Scenes/City/AudioFiles/BassAudio/Dance1Bass[28-36-39]mn_000.aif
new file mode 100755
index 0000000..e221a95
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/BassAudio/Dance1Bass[28-36-39]mn_000.aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/BassAudio/Dance1Bass[40-48-51]mn_000.aif b/City/Scenes/City/AudioFiles/BassAudio/Dance1Bass[40-48-51]mn_000.aif
new file mode 100755
index 0000000..24e322b
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/BassAudio/Dance1Bass[40-48-51]mn_000.aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/BassAudio/Dance1Bass[52-60-63]mn_000.aif b/City/Scenes/City/AudioFiles/BassAudio/Dance1Bass[52-60-63]mn_000.aif
new file mode 100755
index 0000000..e1d53b1
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/BassAudio/Dance1Bass[52-60-63]mn_000.aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[100-108-111]mn_000.aif b/City/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[100-108-111]mn_000.aif
new file mode 100755
index 0000000..6e23d16
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[100-108-111]mn_000.aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[64-72-75]mn_000.aif b/City/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[64-72-75]mn_000.aif
new file mode 100755
index 0000000..74cf041
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[64-72-75]mn_000.aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[76-84-87]mn_000.aif b/City/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[76-84-87]mn_000.aif
new file mode 100755
index 0000000..f8bf860
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[76-84-87]mn_000.aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[88-96-99]mn_000.aif b/City/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[88-96-99]mn_000.aif
new file mode 100755
index 0000000..ce5fb0b
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[88-96-99]mn_000.aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/DrumsAudio/Dance1909kick7[36-36-36].aif b/City/Scenes/City/AudioFiles/DrumsAudio/Dance1909kick7[36-36-36].aif
new file mode 100755
index 0000000..e4cce2d
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/DrumsAudio/Dance1909kick7[36-36-36].aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/DrumsAudio/Dance1909snare3[38-38-38].aif b/City/Scenes/City/AudioFiles/DrumsAudio/Dance1909snare3[38-38-38].aif
new file mode 100755
index 0000000..1107cc5
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/DrumsAudio/Dance1909snare3[38-38-38].aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/DrumsAudio/Dance1SHAKER_1[46-46-46].aif b/City/Scenes/City/AudioFiles/DrumsAudio/Dance1SHAKER_1[46-46-46].aif
new file mode 100755
index 0000000..fb698d7
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/DrumsAudio/Dance1SHAKER_1[46-46-46].aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/DrumsAudio/Dance1closedhat11[39-39-39].aif b/City/Scenes/City/AudioFiles/DrumsAudio/Dance1closedhat11[39-39-39].aif
new file mode 100755
index 0000000..9754819
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/DrumsAudio/Dance1closedhat11[39-39-39].aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/DrumsAudio/Dance1openedhat4[42-42-42].aif b/City/Scenes/City/AudioFiles/DrumsAudio/Dance1openedhat4[42-42-42].aif
new file mode 100755
index 0000000..ee4a48a
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/DrumsAudio/Dance1openedhat4[42-42-42].aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/LeadAudio/keys-good[28-36-39]_001_001mn_000.aif b/City/Scenes/City/AudioFiles/LeadAudio/keys-good[28-36-39]_001_001mn_000.aif
new file mode 100755
index 0000000..79724da
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/LeadAudio/keys-good[28-36-39]_001_001mn_000.aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/LeadAudio/keys-good[40-48-51]_001_001mn_000.aif b/City/Scenes/City/AudioFiles/LeadAudio/keys-good[40-48-51]_001_001mn_000.aif
new file mode 100755
index 0000000..5f204a5
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/LeadAudio/keys-good[40-48-51]_001_001mn_000.aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/LeadAudio/keys-good[52-60-63]_001_001mn_000.aif b/City/Scenes/City/AudioFiles/LeadAudio/keys-good[52-60-63]_001_001mn_000.aif
new file mode 100755
index 0000000..ed4257e
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/LeadAudio/keys-good[52-60-63]_001_001mn_000.aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/LeadAudio/keys-good[64-72-75]_001_001mn_000.aif b/City/Scenes/City/AudioFiles/LeadAudio/keys-good[64-72-75]_001_001mn_000.aif
new file mode 100755
index 0000000..d1bd254
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/LeadAudio/keys-good[64-72-75]_001_001mn_000.aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/LeadAudio/keys-good[76-84-87]_001_001mn_000.aif b/City/Scenes/City/AudioFiles/LeadAudio/keys-good[76-84-87]_001_001mn_000.aif
new file mode 100755
index 0000000..27ef6f3
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/LeadAudio/keys-good[76-84-87]_001_001mn_000.aif
Binary files differ
diff --git a/City/Scenes/City/AudioFiles/LeadAudio/keys-good[88-96-99]_001_001mn_000.aif b/City/Scenes/City/AudioFiles/LeadAudio/keys-good[88-96-99]_001_001mn_000.aif
new file mode 100755
index 0000000..50d5b10
--- /dev/null
+++ b/City/Scenes/City/AudioFiles/LeadAudio/keys-good[88-96-99]_001_001mn_000.aif
Binary files differ
diff --git a/City/Scenes/City/City_Data.txt b/City/Scenes/City/City_Data.txt
new file mode 100644
index 0000000..525850e
--- /dev/null
+++ b/City/Scenes/City/City_Data.txt
@@ -0,0 +1,8 @@
+Tempo = 120
+Key = A
+Mode = minor
+Volume = 1
+Pitch = 0.6
+Timbre = 0.7
+
+
diff --git a/City/Scenes/City/MidiFiles/Bass.mid b/City/Scenes/City/MidiFiles/Bass.mid
new file mode 100755
index 0000000..9cf6010
--- /dev/null
+++ b/City/Scenes/City/MidiFiles/Bass.mid
Binary files differ
diff --git a/City/Scenes/City/MidiFiles/Chords.mid b/City/Scenes/City/MidiFiles/Chords.mid
new file mode 100755
index 0000000..7fac8ce
--- /dev/null
+++ b/City/Scenes/City/MidiFiles/Chords.mid
Binary files differ
diff --git a/City/Scenes/City/MidiFiles/Drums.mid b/City/Scenes/City/MidiFiles/Drums.mid
new file mode 100755
index 0000000..ea7a047
--- /dev/null
+++ b/City/Scenes/City/MidiFiles/Drums.mid
Binary files differ
diff --git a/City/Scenes/City/MidiFiles/Lead.mid b/City/Scenes/City/MidiFiles/Lead.mid
new file mode 100755
index 0000000..206f2ea
--- /dev/null
+++ b/City/Scenes/City/MidiFiles/Lead.mid
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/BassAudio/BassC2[41-48-50].aif b/City/Scenes/Country/AudioFiles/BassAudio/BassC2[41-48-50].aif
new file mode 100644
index 0000000..3e3fd98
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/BassAudio/BassC2[41-48-50].aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/BassAudio/BassC3[59-60-62].aif b/City/Scenes/Country/AudioFiles/BassAudio/BassC3[59-60-62].aif
new file mode 100644
index 0000000..bd3ab7f
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/BassAudio/BassC3[59-60-62].aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/BassAudio/BassC4[70-72-78].aif b/City/Scenes/Country/AudioFiles/BassAudio/BassC4[70-72-78].aif
new file mode 100644
index 0000000..f9ec61b
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/BassAudio/BassC4[70-72-78].aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/BassAudio/BassF#2[51-54-58].aif b/City/Scenes/Country/AudioFiles/BassAudio/BassF#2[51-54-58].aif
new file mode 100644
index 0000000..c099d56
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/BassAudio/BassF#2[51-54-58].aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/ChordsAudio/epnoC4[70-72-73].aif b/City/Scenes/Country/AudioFiles/ChordsAudio/epnoC4[70-72-73].aif
new file mode 100644
index 0000000..475ac71
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/ChordsAudio/epnoC4[70-72-73].aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/ChordsAudio/epnoC5[82-84-90].aif b/City/Scenes/Country/AudioFiles/ChordsAudio/epnoC5[82-84-90].aif
new file mode 100644
index 0000000..f89023c
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/ChordsAudio/epnoC5[82-84-90].aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/ChordsAudio/epnoE3[63-64-65].aif b/City/Scenes/Country/AudioFiles/ChordsAudio/epnoE3[63-64-65].aif
new file mode 100644
index 0000000..fd1a6c5
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/ChordsAudio/epnoE3[63-64-65].aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/ChordsAudio/epnoE4[74-76-77].aif b/City/Scenes/Country/AudioFiles/ChordsAudio/epnoE4[74-76-77].aif
new file mode 100644
index 0000000..b4d5964
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/ChordsAudio/epnoE4[74-76-77].aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/DrumsAudio/C#1_rim_shot.aif b/City/Scenes/Country/AudioFiles/DrumsAudio/C#1_rim_shot.aif
new file mode 100755
index 0000000..beea298
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/DrumsAudio/C#1_rim_shot.aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/DrumsAudio/C1_kick_drum.aif b/City/Scenes/Country/AudioFiles/DrumsAudio/C1_kick_drum.aif
new file mode 100644
index 0000000..2b00005
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/DrumsAudio/C1_kick_drum.aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/DrumsAudio/D1_snare_drum.aif b/City/Scenes/Country/AudioFiles/DrumsAudio/D1_snare_drum.aif
new file mode 100755
index 0000000..cee687f
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/DrumsAudio/D1_snare_drum.aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/DrumsAudio/F#1_closed_hi_hat.aif b/City/Scenes/Country/AudioFiles/DrumsAudio/F#1_closed_hi_hat.aif
new file mode 100755
index 0000000..543d2e6
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/DrumsAudio/F#1_closed_hi_hat.aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/LeadAudio/B2_guitar.aif b/City/Scenes/Country/AudioFiles/LeadAudio/B2_guitar.aif
new file mode 100644
index 0000000..9a06c2f
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/LeadAudio/B2_guitar.aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/LeadAudio/B3_guitar.aif b/City/Scenes/Country/AudioFiles/LeadAudio/B3_guitar.aif
new file mode 100644
index 0000000..a229b76
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/LeadAudio/B3_guitar.aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/LeadAudio/E2_guitar.aif b/City/Scenes/Country/AudioFiles/LeadAudio/E2_guitar.aif
new file mode 100644
index 0000000..0df7f1e
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/LeadAudio/E2_guitar.aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/LeadAudio/E3_guitar.aif b/City/Scenes/Country/AudioFiles/LeadAudio/E3_guitar.aif
new file mode 100644
index 0000000..e8f5be5
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/LeadAudio/E3_guitar.aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/LeadAudio/E4_guitar.aif b/City/Scenes/Country/AudioFiles/LeadAudio/E4_guitar.aif
new file mode 100644
index 0000000..9909889
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/LeadAudio/E4_guitar.aif
Binary files differ
diff --git a/City/Scenes/Country/AudioFiles/LeadAudio/E5_guitar.aif b/City/Scenes/Country/AudioFiles/LeadAudio/E5_guitar.aif
new file mode 100644
index 0000000..f1f7076
--- /dev/null
+++ b/City/Scenes/Country/AudioFiles/LeadAudio/E5_guitar.aif
Binary files differ
diff --git a/City/Scenes/Country/Country_Data.txt b/City/Scenes/Country/Country_Data.txt
new file mode 100644
index 0000000..4dcdc4e
--- /dev/null
+++ b/City/Scenes/Country/Country_Data.txt
@@ -0,0 +1,5 @@
+Tempo = 120
+Key = E
+Mode = major
+
+
diff --git a/City/Scenes/Country/MidiFiles/Bass.mid b/City/Scenes/Country/MidiFiles/Bass.mid
new file mode 100755
index 0000000..335ce31
--- /dev/null
+++ b/City/Scenes/Country/MidiFiles/Bass.mid
Binary files differ
diff --git a/City/Scenes/Country/MidiFiles/Chords.mid b/City/Scenes/Country/MidiFiles/Chords.mid
new file mode 100755
index 0000000..9b2b819
--- /dev/null
+++ b/City/Scenes/Country/MidiFiles/Chords.mid
Binary files differ
diff --git a/City/Scenes/Country/MidiFiles/Drums.mid b/City/Scenes/Country/MidiFiles/Drums.mid
new file mode 100755
index 0000000..1457736
--- /dev/null
+++ b/City/Scenes/Country/MidiFiles/Drums.mid
Binary files differ
diff --git a/City/Scenes/Country/MidiFiles/Lead.mid b/City/Scenes/Country/MidiFiles/Lead.mid
new file mode 100755
index 0000000..52b61fd
--- /dev/null
+++ b/City/Scenes/Country/MidiFiles/Lead.mid
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/BassAudio/BassC2[41-48-50].aif b/City/Scenes/Latin/AudioFiles/BassAudio/BassC2[41-48-50].aif
new file mode 100644
index 0000000..f510c60
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/BassAudio/BassC2[41-48-50].aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/BassAudio/BassC3[59-60-62].aif b/City/Scenes/Latin/AudioFiles/BassAudio/BassC3[59-60-62].aif
new file mode 100644
index 0000000..e1c38ad
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/BassAudio/BassC3[59-60-62].aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/BassAudio/BassC4[70-72-78].aif b/City/Scenes/Latin/AudioFiles/BassAudio/BassC4[70-72-78].aif
new file mode 100644
index 0000000..1c3d78d
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/BassAudio/BassC4[70-72-78].aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/BassAudio/BassF#2[51-54-58].aif b/City/Scenes/Latin/AudioFiles/BassAudio/BassF#2[51-54-58].aif
new file mode 100644
index 0000000..ab7abe4
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/BassAudio/BassF#2[51-54-58].aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/ChordsAudio/Organ C4 22 16 mono.aif b/City/Scenes/Latin/AudioFiles/ChordsAudio/Organ C4 22 16 mono.aif
new file mode 100644
index 0000000..28f851c
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/ChordsAudio/Organ C4 22 16 mono.aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/ChordsAudio/Organ C5 22 16 mono.aif b/City/Scenes/Latin/AudioFiles/ChordsAudio/Organ C5 22 16 mono.aif
new file mode 100644
index 0000000..58d58e2
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/ChordsAudio/Organ C5 22 16 mono.aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/ChordsAudio/Organ G3 22 16 mono.aif b/City/Scenes/Latin/AudioFiles/ChordsAudio/Organ G3 22 16 mono.aif
new file mode 100644
index 0000000..d4a8320
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/ChordsAudio/Organ G3 22 16 mono.aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/ChordsAudio/Organ G4 22 16 mono.aif b/City/Scenes/Latin/AudioFiles/ChordsAudio/Organ G4 22 16 mono.aif
new file mode 100644
index 0000000..6aad12c
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/ChordsAudio/Organ G4 22 16 mono.aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/DrumsAudio/Bongo C#3 22 16 Mono.aif b/City/Scenes/Latin/AudioFiles/DrumsAudio/Bongo C#3 22 16 Mono.aif
new file mode 100644
index 0000000..d63f7c1
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/DrumsAudio/Bongo C#3 22 16 Mono.aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/DrumsAudio/Bongo C3 22 16 Mono.aif b/City/Scenes/Latin/AudioFiles/DrumsAudio/Bongo C3 22 16 Mono.aif
new file mode 100644
index 0000000..546e609
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/DrumsAudio/Bongo C3 22 16 Mono.aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/DrumsAudio/Bongo D3 22 16 Mono.aif b/City/Scenes/Latin/AudioFiles/DrumsAudio/Bongo D3 22 16 Mono.aif
new file mode 100644
index 0000000..e947c9a
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/DrumsAudio/Bongo D3 22 16 Mono.aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/DrumsAudio/ClosedhatF#1.aif b/City/Scenes/Latin/AudioFiles/DrumsAudio/ClosedhatF#1.aif
new file mode 100644
index 0000000..f170804
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/DrumsAudio/ClosedhatF#1.aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/DrumsAudio/Kick-C1.aif b/City/Scenes/Latin/AudioFiles/DrumsAudio/Kick-C1.aif
new file mode 100644
index 0000000..7204bf6
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/DrumsAudio/Kick-C1.aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/DrumsAudio/Shaker 22 16 Mono.aif b/City/Scenes/Latin/AudioFiles/DrumsAudio/Shaker 22 16 Mono.aif
new file mode 100644
index 0000000..cb70adf
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/DrumsAudio/Shaker 22 16 Mono.aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/DrumsAudio/Snare-D1.aif b/City/Scenes/Latin/AudioFiles/DrumsAudio/Snare-D1.aif
new file mode 100644
index 0000000..de03f64
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/DrumsAudio/Snare-D1.aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/DrumsAudio/TambF#2.aif b/City/Scenes/Latin/AudioFiles/DrumsAudio/TambF#2.aif
new file mode 100644
index 0000000..0eb9de9
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/DrumsAudio/TambF#2.aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets A4 22 16 mono.aif b/City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets A4 22 16 mono.aif
new file mode 100644
index 0000000..df3c59a
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets A4 22 16 mono.aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets C4 22 16 mono.aif b/City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets C4 22 16 mono.aif
new file mode 100644
index 0000000..918da75
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets C4 22 16 mono.aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets E3 22 16 mono.aif b/City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets E3 22 16 mono.aif
new file mode 100644
index 0000000..7f8ffd9
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets E3 22 16 mono.aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets F4 22 16 mono.aif b/City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets F4 22 16 mono.aif
new file mode 100644
index 0000000..9e5bb5f
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets F4 22 16 mono.aif
Binary files differ
diff --git a/City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets G3 22 16 mono.aif b/City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets G3 22 16 mono.aif
new file mode 100644
index 0000000..15f0639
--- /dev/null
+++ b/City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets G3 22 16 mono.aif
Binary files differ
diff --git a/City/Scenes/Latin/Latin_Data.txt b/City/Scenes/Latin/Latin_Data.txt
new file mode 100644
index 0000000..b9f739c
--- /dev/null
+++ b/City/Scenes/Latin/Latin_Data.txt
@@ -0,0 +1,3 @@
+Tempo = 164
+Key = F
+Mode = major
diff --git a/City/Scenes/Latin/MidiFiles/Bass.mid b/City/Scenes/Latin/MidiFiles/Bass.mid
new file mode 100644
index 0000000..cbc12a4
--- /dev/null
+++ b/City/Scenes/Latin/MidiFiles/Bass.mid
Binary files differ
diff --git a/City/Scenes/Latin/MidiFiles/Chords.mid b/City/Scenes/Latin/MidiFiles/Chords.mid
new file mode 100644
index 0000000..d27fb73
--- /dev/null
+++ b/City/Scenes/Latin/MidiFiles/Chords.mid
Binary files differ
diff --git a/City/Scenes/Latin/MidiFiles/Drums.mid b/City/Scenes/Latin/MidiFiles/Drums.mid
new file mode 100644
index 0000000..4ab7c9c
--- /dev/null
+++ b/City/Scenes/Latin/MidiFiles/Drums.mid
Binary files differ
diff --git a/City/Scenes/Latin/MidiFiles/Lead.mid b/City/Scenes/Latin/MidiFiles/Lead.mid
new file mode 100644
index 0000000..407d400
--- /dev/null
+++ b/City/Scenes/Latin/MidiFiles/Lead.mid
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/BassAudio/BassC2[41-48-50].aif b/City/Scenes/Reggae/AudioFiles/BassAudio/BassC2[41-48-50].aif
new file mode 100644
index 0000000..3e3fd98
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/BassAudio/BassC2[41-48-50].aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/BassAudio/BassC3[59-60-62].aif b/City/Scenes/Reggae/AudioFiles/BassAudio/BassC3[59-60-62].aif
new file mode 100644
index 0000000..bd3ab7f
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/BassAudio/BassC3[59-60-62].aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/BassAudio/BassC4[70-72-78].aif b/City/Scenes/Reggae/AudioFiles/BassAudio/BassC4[70-72-78].aif
new file mode 100644
index 0000000..f9ec61b
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/BassAudio/BassC4[70-72-78].aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/BassAudio/BassF#2[51-54-58].aif b/City/Scenes/Reggae/AudioFiles/BassAudio/BassF#2[51-54-58].aif
new file mode 100644
index 0000000..c099d56
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/BassAudio/BassF#2[51-54-58].aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoC4[70-72-73].aif b/City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoC4[70-72-73].aif
new file mode 100644
index 0000000..475ac71
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoC4[70-72-73].aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoC5[82-84-90].aif b/City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoC5[82-84-90].aif
new file mode 100644
index 0000000..f89023c
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoC5[82-84-90].aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoE3[63-64-65].aif b/City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoE3[63-64-65].aif
new file mode 100644
index 0000000..fd1a6c5
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoE3[63-64-65].aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoE4[74-76-77].aif b/City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoE4[74-76-77].aif
new file mode 100644
index 0000000..b4d5964
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoE4[74-76-77].aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/DrumsAudio/ClosedhatF#1.aif b/City/Scenes/Reggae/AudioFiles/DrumsAudio/ClosedhatF#1.aif
new file mode 100644
index 0000000..f170804
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/DrumsAudio/ClosedhatF#1.aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/DrumsAudio/Kick-C1.aif b/City/Scenes/Reggae/AudioFiles/DrumsAudio/Kick-C1.aif
new file mode 100644
index 0000000..7204bf6
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/DrumsAudio/Kick-C1.aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/DrumsAudio/OpenhatA#1.aif b/City/Scenes/Reggae/AudioFiles/DrumsAudio/OpenhatA#1.aif
new file mode 100644
index 0000000..4f5f0b4
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/DrumsAudio/OpenhatA#1.aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/DrumsAudio/Snare-D1.aif b/City/Scenes/Reggae/AudioFiles/DrumsAudio/Snare-D1.aif
new file mode 100644
index 0000000..de03f64
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/DrumsAudio/Snare-D1.aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/DrumsAudio/TambF#2.aif b/City/Scenes/Reggae/AudioFiles/DrumsAudio/TambF#2.aif
new file mode 100644
index 0000000..0eb9de9
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/DrumsAudio/TambF#2.aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/LeadAudio/B2_guitar.aif b/City/Scenes/Reggae/AudioFiles/LeadAudio/B2_guitar.aif
new file mode 100644
index 0000000..9a06c2f
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/LeadAudio/B2_guitar.aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/LeadAudio/B3_guitar.aif b/City/Scenes/Reggae/AudioFiles/LeadAudio/B3_guitar.aif
new file mode 100644
index 0000000..a229b76
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/LeadAudio/B3_guitar.aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/LeadAudio/E2_guitar.aif b/City/Scenes/Reggae/AudioFiles/LeadAudio/E2_guitar.aif
new file mode 100644
index 0000000..0df7f1e
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/LeadAudio/E2_guitar.aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/LeadAudio/E3_guitar.aif b/City/Scenes/Reggae/AudioFiles/LeadAudio/E3_guitar.aif
new file mode 100644
index 0000000..e8f5be5
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/LeadAudio/E3_guitar.aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/LeadAudio/E4_guitar.aif b/City/Scenes/Reggae/AudioFiles/LeadAudio/E4_guitar.aif
new file mode 100644
index 0000000..9909889
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/LeadAudio/E4_guitar.aif
Binary files differ
diff --git a/City/Scenes/Reggae/AudioFiles/LeadAudio/E5_guitar.aif b/City/Scenes/Reggae/AudioFiles/LeadAudio/E5_guitar.aif
new file mode 100644
index 0000000..f1f7076
--- /dev/null
+++ b/City/Scenes/Reggae/AudioFiles/LeadAudio/E5_guitar.aif
Binary files differ
diff --git a/City/Scenes/Reggae/MidiFiles/Bass.mid b/City/Scenes/Reggae/MidiFiles/Bass.mid
new file mode 100644
index 0000000..be414ec
--- /dev/null
+++ b/City/Scenes/Reggae/MidiFiles/Bass.mid
Binary files differ
diff --git a/City/Scenes/Reggae/MidiFiles/Chords.mid b/City/Scenes/Reggae/MidiFiles/Chords.mid
new file mode 100644
index 0000000..1feebaa
--- /dev/null
+++ b/City/Scenes/Reggae/MidiFiles/Chords.mid
Binary files differ
diff --git a/City/Scenes/Reggae/MidiFiles/Drums.mid b/City/Scenes/Reggae/MidiFiles/Drums.mid
new file mode 100644
index 0000000..bf2f5be
--- /dev/null
+++ b/City/Scenes/Reggae/MidiFiles/Drums.mid
Binary files differ
diff --git a/City/Scenes/Reggae/MidiFiles/Lead.mid b/City/Scenes/Reggae/MidiFiles/Lead.mid
new file mode 100644
index 0000000..2a0d2be
--- /dev/null
+++ b/City/Scenes/Reggae/MidiFiles/Lead.mid
Binary files differ
diff --git a/City/Scenes/Reggae/Reggae_Data.txt b/City/Scenes/Reggae/Reggae_Data.txt
new file mode 100644
index 0000000..cc23d84
--- /dev/null
+++ b/City/Scenes/Reggae/Reggae_Data.txt
@@ -0,0 +1,3 @@
+Tempo = 164
+Key = G
+Mode = major
diff --git a/City/Tracks.py b/City/Tracks.py
new file mode 100755
index 0000000..de9e3d9
--- /dev/null
+++ b/City/Tracks.py
@@ -0,0 +1,512 @@
+#!/usr/bin/env python
+"""
+This module defines Notes, Scores, Beats and Players and a convenience class for midi file parsing.
+TEST THIS PLAYER!
+"""
+
+#Note has no instrument number
+#This is determined by the Track
+ONSETINDEX = 0
+DURINDEX = 1
+VELINDEX = 2
+PITINDEX = 3
+
+
+import bisect, random, math, logging, thread
+from midiImport import *
+from CsHelpers import *
+
+log = logging.getLogger( 'City run' )
+log.setLevel( logging.DEBUG )
+
+
+class Note:
+ def __init__(self, note = [0,0,0,0]):
+ self.data = note[:]
+ self.index = 0
+ def __repr__(self):
+ return 'Note: '+' '.join(map(str,(self.data)))
+ def __getitem__(self,index):
+ return self.data[index]
+ def __setitem__(self,index, value):
+ self.data[index] = value
+ def __getslice__(self,a,b):
+ return self.data[a:b]
+ def __len__(self):
+ return len(self.data)
+ def onset(self):
+ return self[ONSETINDEX]
+ def duration(self):
+ return self[DURINDEX]
+ def velocity(self):
+ return self[VELINDEX]
+ def pitch(self):
+ return self[PITINDEX]
+ def setOnset(self, val):
+ self[ONSETINDEX] = val
+ def setDuration(self, val):
+ self[DURINDEX] = val
+ def setVelocity(self, val):
+ self[VELINDEX] = val
+ def setPitch(self, val):
+ self[PITINDEX] = val
+ def offTime(self):
+ return self.duration() + self.onset()
+
+
+class Track:
+ def __init__(self, data = []):
+ "A Track can be initialised with a list of notes"
+ self.data = data[:]
+ self.index = 0
+ self.instrument = 1
+ def setInstrument(self, num):
+ self.instrument = num
+ def addNote(self, note):
+ index = bisect.bisect_left(self.data, [note[ONSETINDEX]])
+ self.data.insert(index, note)
+ def __delitem__(self, index):
+ del(self.data[index])
+ def __getitem__(self, index):
+ if len(self) == 0:
+ return False
+ else:
+ return self.data[index]
+ def __getslice__(self, a,b):
+ return Track(self.data[a:b])
+ def __len__(self):
+ return len(self.data)
+ def resetIterator(self, ndx = 0):
+ self.index = ndx
+ def findNoteAtTime(self, time):
+ return bisect.bisect_left([n.data for n in self.data], [time])
+ def getNoteAtTime(self, time):
+ return self.data[self.findNoteAtTime(time)]
+ def getParameter(self, ndx):
+ return [i[ndx] for i in self.data]
+ def getOnsets(self):
+ return self.getParameter(ONSETINDEX)
+ def getDurations(self):
+ return self.getParameter(DURINDEX)
+ def getVelocities(self):
+ return self.getParameter(VELINDEX)
+ def getPitches(self):
+ return self.getParameter(PITINDEX)
+ def modParameter(self, pndx, mult):
+ "a bit of a hack, while I try and work out tempo"
+ for n in self:
+ n[pndx] *= mult
+ def __repr__(self):
+ return 'Track :' + str(self.data)
+
+
+class Beat(Track):
+ def __init__(self, beatnum, offset = 0, data = Track()):
+ "Beat can be initialised with an existing track. The track is automatically sliced"
+ self.data = data.data[:]
+ self.index = 0
+ self.instrument = 1
+ self.offset = offset
+ self.beatnum = beatnum
+ def setBeatNum(self, beatnum):
+ self.beatnum = beatnum
+ def setOffset(self, beatnum):
+ self.offset = offset
+ def nudgeParameter(self, parameter, shiftAmount):
+ "returns a new beat with onsets shifted by shiftAmount"
+ newbeat = Beat(self.beatnum)
+ for i in self.data:
+ newp = i[parameter] + shiftAmount
+ newl = []
+ for p in range(len(i)):
+ if p == parameter:
+ newl.append(newp)
+ else: newl.append(i[p])
+ newNote = Note(newl)
+ newbeat.addNote(newNote)
+ return newbeat
+ def relativeOnsets(self, start = 0):
+ shift = -self[0].onset() - self.offset
+ return self.nudgeParameter(ONSETINDEX, shift + start)
+ def __repr__(self):
+ return 'Beat ' + str(self.beatnum) + ':' + str(self.data)
+
+def beat(track, time, beatlen, beatnum):
+ "beat function extracts a beat object from a track, and assigns it a 'beat' number"
+ ndx = track.findNoteAtTime(time)
+ result = Beat(beatnum)
+ if ndx >= len(track): return result
+ timeOffset = track[ndx].onset() - time
+ if timeOffset > beatlen:
+ return result
+ else:
+ for n in track[ndx: ]:
+ if n.onset() < track[ndx].onset() + beatlen:
+ result.addNote(n)
+ else: break
+ return result
+
+class Midi2Score:
+ def __init__(self, path):
+ midiData = MidiFile()
+ midiData.open(path)
+ midiData.read()
+ midiData.close()
+ self.midiData = midiData
+ self.ticksPerBeat = midiData.ticksPerQuarterNote
+ def numTracks(self):
+ return len(self.midiData.tracks)
+ def getTrack(self, ndx):
+ return self.midiData.tracks[ndx]
+ def time2beats(self,time):
+ return float(time) / self.ticksPerBeat
+ def findNO(self, noff, nlst):
+ pitlst = [n.pitch() for n in reversed(nlst)]
+ return (len(pitlst) - 1) - pitlst.index(noff.pitch)
+ def midiTrack2Notes(self, track):
+ "returns a list of notes from a track"
+ result = []
+ for event in track.events:
+ if event.type == "NOTE_ON":
+ result.append(Note([self.time2beats(event.time), 0, event.velocity, event.pitch]))
+ elif event.type == "NOTE_OFF":
+ ndx = self.findNO(event, result)
+ dur = self.time2beats(event.time) - result[ndx].onset()
+ result[ndx].setDuration(dur)
+ else:
+ pass
+ return result
+ def midiTrack2ScoreTrack(self, mtrack):
+ "returns a Track object from a midi file"
+ notelist = self.midiTrack2Notes(mtrack)
+ return Track(notelist)
+
+
+class Scale:
+ "a class for manipulating pitch data"
+ def __init__(self, keyname, modality):
+ "where keyname is a letter and modality is a string e.g. Scale('C#','minor'))"
+ self.key = keyname
+ self.modality = modality
+ self.chromatic = range(12, 109)
+ self.keyMap = {'C':0, 'C#':1,'D':2, 'D#':3, 'E':4, 'F':5, 'F#':6, 'G':7, 'G#':8,'A':9,'A#':10, 'B':11}
+ self.scale = self.Scalemap(self.Mode(keyname, modality))
+ self.subscale = self.Scalemap(self.Mode(keyname, "subscale"))
+ def Scalemap(self, scale):
+ lim = True
+ oct = 0
+ result = []
+ stop = len(self.chromatic)
+ while lim:
+ for i in scale:
+ ndx = i + oct * 12
+ if ndx >= stop:
+ lim = False
+ break
+ else:
+ result.append(self.chromatic[ndx])
+ oct += 1
+ return result
+ def Transpose(self, incr, mode):
+ result = [(n + incr) % 12 for n in mode]
+ result.sort()
+ return result
+ def Mode(self, key, name):
+ if name == "chromatic":
+ return range(12)
+ elif name == "major":
+ return self.Transpose(self.keyMap[key], [0,2,4,5,7,9,11])
+ elif name == "minor":
+ return self.Transpose(self.keyMap[key], [0,2,3,5,7,8,10])
+ elif name == "minor pentatonic":
+ return self.Transpose(self.keyMap[key], [0,2,3,5,7,8])
+ elif name == "subscale":
+ return self.Transpose(self.keyMap[key], [0,2,5,7])
+ else: return []
+ def pitchMap(self, pitch, subscale = False):
+ "Integers only. Given a pitch, recalculates a new pitch within the key and scale"
+ if subscale:
+ scale = self.subscale
+ else:
+ scale = self.scale
+ if pitch in scale:
+ return pitch
+ else:
+ pup = pitch + 1
+ pdown = pitch - 1
+ result = None
+ for i in xrange(len(scale)):
+ if pup in scale:
+ result = pup
+ break
+ elif pdown in scale:
+ result = pdown
+ break
+ else:
+ pup += 1
+ pdown -= 1
+ return result
+ def basePit(self, strack):
+ "return the lowest tonic note in the track"
+ tonics = filter(lambda x: x % 12 == self.keyMap[self.key], strack.getPitches())
+ if tonics:
+ return min(tonics)
+ else:
+ return self.keyMap[self.key] + 48
+
+
+class beatDebugPlayer:
+ "This player expects beats to be relative to zero already"
+ TRACKDATA_NDX = 0
+ INSNUM_NDX = 1
+ def __init__(self, cssynth, timer, perimeter, scaleObj):
+ "params is the parameter object"
+ print "THREAD ID: PLAYER INIT" , thread.get_ident()
+ self.cs = cssynth
+ self.beat = 0
+ self.scoreTracks = {}
+ self.drumPitches = []
+ self.trackMap = {}
+ self.activity = {}
+ self.timer = timer
+ self.perimeter = perimeter
+ self.scale = scaleObj
+ self.basepits = {}
+ self.tempoMult = 1 #the tempo() method changes this
+ self.den = 0
+ self.beatlimit = 0
+ self.sendSync = False
+ self.loop_idcounter = 1
+ self.mutelist = []
+ self.picture_cycle = []
+ self.frozen = False
+ self.mute_all = False
+ def pause(self):
+ self.mute_all = True
+ def oldpause(self, *lid):
+ if lid and self.activity.has_key(lid[0]):
+ self.activity[lid[0]] = 'Pause'
+ return True
+ else:
+ #if no loop id is specified, or the id is just plain wrong, try and stop the last loop added set to play.
+ for k in sorted(self.activity.keys(), reverse = True):
+ if self.activity[k] == 'Play':
+ self.activity[k] = 'Pause'
+ return True
+ else:
+ return False
+ def Stop(self):
+ self.activity = 'Stop'
+ self.resetBeat()
+ self.cs.Stop()
+ def Cease(self, *lid):
+ if lid and self.activity.has_key(lid[0]):
+ self.activity[lid[0]] = 'Cease'
+ return True
+ else:
+ for k in sorted(self.activity.keys(), reverse = True):
+ if self.activity[k] == 'Play' or self.activity[k] == 'Pause':
+ self.activity[k] = 'Cease'
+ log.info("Ceasing loop id %s" %k)
+ return True
+ else:
+ return False
+ def resume(self):
+ self.mute_all = False
+ def oldresume(self, *lid):
+ if lid and self.activity.has_key(lid[0]):
+ self.activity[lid[0]] = 'Play'
+ return True
+ else:
+ for k in sorted(self.activity.keys(), reverse = True):
+ if self.activity[k] == 'Pause':
+ self.activity[k] = 'Play'
+ print "resuming id ", k
+ return True
+ else:
+ return False
+ def resetBeat(self, *num):
+ if num:
+ self.beat = num[0]
+ else:
+ self.beat = 0
+ def Track2beatList(self, Strack, beatlen, tracklen):
+ "return a list of beats from the track, reletavised to zero"
+ result = []
+ for i in range(tracklen):
+ if i > len(Strack) - 1:
+ result.append(Beat(i))
+ else:
+ result.append(beat(Strack, i, beatlen - 0.01, i))
+ return [(b.relativeOnsets(b[0].onset() % 1) if b[0] else b) for b in result]
+ def beatInstrumentMap(self, beatlength, **kargs):
+ "kargs is a dictionary. Assign beatlists to Csound instruments kbeats. Keys = 5"
+ for key in kargs:
+ strack = kargs[key][self.TRACKDATA_NDX]
+ self.scoreTracks[key] = strack
+ if key == 'Drums':
+ self.drumPitches = sorted(list(set(self.scoreTracks["Drums"].getPitches())))
+ tracklen = int(sorted(list(set(self.scoreTracks["Drums"].getOnsets())))[-1])
+ while tracklen % 4:
+ tracklen += 1
+ kargs[key][self.TRACKDATA_NDX] = self.Track2beatList(strack, beatlength, tracklen)
+ self.basepits[key] = self.scale.basePit(strack)
+ self.trackMap = kargs
+ self.beatlimit = self.beatLimit()
+ def getInstrument(self, trackname):
+ "Get an Instrument associated with the track"
+ return self.trackMap[trackname][self.INSNUM_NDX]
+ def getBeatData(self, trackname):
+ "The following three functions ought to be improved"
+ return self.trackMap[trackname][self.TRACKDATA_NDX]
+ def beatLists(self):
+ "return a list of all the trackdata"
+ return [self.getBeatData(keyname) for keyname in self.trackMap.keys()]
+ def beatLimit(self):
+ return len(self.beatLists()[0])
+ def setBPM(self, bpm):
+ "tempomult * 1 = 120bpm. Turns out ticksPerQuarter in midiImport.py always returns 480 "
+ print "bpm, " ,bpm, bpm / 120.0
+ self.tempoMult = (60.0 / bpm)
+ def tempo(self):
+ "sets current tempo"
+ print "tempo"
+ tempoparam = self.perimeter.getValue('Tempo', 'Drums') #Only look at one parameter, since tempo is global.
+ self.tempoMult = 1/(tempoparam + 1.5)
+ def articulate(self, insname):
+ if insname == 'Drums':
+ return 1
+ else:
+ pval = self.perimeter.getValue('Length', insname) + 0.5
+ if pval < 1:
+ return pval
+ else:
+ return rescale(pval, 1, 1.5, 1, 6)
+ def noteChecker(self, keyname, t, modulo, varGreaterThan, varLessThan):
+ dval = self.density(keyname)
+ densval = (dval + (random.uniform(-.112, .112)) if dval != 1 or 0 else (0.99 if dval == 1 else 0.01))
+ return (True if t % modulo == 0 and varGreaterThan <= limit(densval, 0, 1) < varLessThan else False)
+ def density(self, insname):
+ "could introduce some randomness here"
+ if insname == "Keys":
+ regions = [0, 1, 0.75, 2]
+ else:
+ regions = [0, 0.5, 0.5, 1, 0.75, 2]
+ val = 1 - self.perimeter.getValue('Density', insname)
+ valnew = rescale(val, 0, 1, 0, len(regions) - 1)
+ valnew -= random.uniform(0, 0.6)
+ if valnew < 0: valnew = 0
+ return regions[int(round(valnew))]
+ def pitchCalc(self, insname, pitch, onset):
+ "Calculate a new pitch based on the old"
+ pitchparam = self.perimeter.getValue('Pitch', insname)
+ if pitchparam > 0.4 and pitchparam < 0.6:
+ return pitch
+ if insname == 'Drums':
+ l = self.drumPitches
+ if pitchparam <= 0.4:
+ if pitch in l[0:int(math.ceil(len(l) * (pitchparam + 0.01) * 2))]:
+ return pitch
+ else:
+ return False
+ elif pitchparam >= 0.6:
+ if pitch in l[int(math.floor((len(l) - 1) * (pitchparam - 0.5) * 2)):len(l)]:
+ return pitch
+ else: return False
+ else:
+ return False
+ else:
+ basepit = self.basepits[insname]
+ pitchparam = self.perimeter.getValue('Pitch', insname)
+ newpitch = basepit + (pitch - basepit) * pitchparam * 2
+ if onset % 1 == 0:
+ return self.scale.pitchMap(int(round(newpitch)), True)
+ else:
+ return self.scale.pitchMap(int(round(newpitch)))
+ def freeze(self):
+ "stop access to all input into the player, usually done when reloading a scene"
+ self.cs.perf.SetProcessCallback(lambda: None, None)
+ for lids in self.activity:
+ self.activity[lids] = 'Cease'
+ self.frozen = True
+ print "frozen = ", self.frozen
+ def playBeat(self, time, beatnum):
+ if self.frozen: return None
+ if self.sendSync:
+ self.timer.schedEvent(time, olpcgames.mesh.broadcast, "Beat|%s" %beatnum)
+ self.sendSync = False
+ if self.picture_cycle:
+ activity = self.picture_cycle[0]
+ pics = activity.snap_store
+ updated = self.picture_cycle[1]
+ if not pics:
+ pass
+ elif updated:
+ ndx = len(pics) - 1
+ activity.feedbackgroundImage = pics[ndx]
+ else:
+ ndx = beatnum % len(pics)
+ activity.feedbackgroundImage = pics[ndx]
+ self.picture_cycle[1] = False
+ for keyname in [i for i in self.trackMap.keys() if i not in self.mutelist]:
+ ins = self.getInstrument(keyname)
+ beatCollect = self.getBeatData(keyname)[beatnum]
+ if beatCollect:
+ for note in beatCollect:
+ den = self.density(keyname)
+ pitch = self.pitchCalc(keyname, note[PITINDEX], note[ONSETINDEX])
+ if not pitch:
+ pass
+ elif den == 0:
+ self.cs.playParams(ins, (note[ONSETINDEX] * self.tempoMult) + (time - self.cs.perfTime()),
+ (note[DURINDEX] * self.articulate(keyname) if keyname == 'Drums' or keyname == 'Keys'
+ else note[DURINDEX] * self.tempoMult * self.articulate(keyname)),
+ note[VELINDEX],
+ pitch)
+ elif (note[ONSETINDEX] + beatCollect.beatnum) % den == 0:
+ self.cs.playParams(ins, (note[ONSETINDEX] * self.tempoMult) + (time - self.cs.perfTime()),
+ max(note[DURINDEX] * self.tempoMult, (den * 0.8) * 0.5) * self.articulate(keyname),
+ note[VELINDEX],
+ pitch)
+ else:
+ pass
+ def playLoop(self, time, reset_beat = False, loop_ID = False):
+ if self.frozen: return None
+ elif loop_ID:
+ lid = loop_ID
+ else:
+ lid = self.loop_idcounter
+ self.activity.update({lid:'Play'})
+ self.loop_idcounter += 1
+ if reset_beat:
+ self.beat = reset_beat
+ #self.tempo() # was used to dynamically calculate tempo. Now disabled.
+ if self.mute_all:
+ self.timer.schedEvent(time + 0.5 * self.tempoMult, self.playLoop, time + 1 * self.tempoMult, False, lid)
+ elif self.activity[lid] == 'Play':
+ self.playBeat(time, self.beat)
+ self.beat = (self.beat + 1) % self.beatlimit
+ self.timer.schedEvent(time, self.playLoop, time + (1 * self.tempoMult), False, lid)
+ elif self.activity[lid] == 'Pause':
+ self.timer.schedEvent(time + 0.5 * self.tempoMult, self.playLoop, time + 1 * self.tempoMult, False, lid)
+ elif self.activity[lid] == 'Cease':
+ pass
+ else:
+ self.resume()
+
+
+if __name__ == '__main__':
+ print "running test code for Tracks.py \n"
+ mpath = raw_input("enter a path to a midi file :")
+ mfile = Midi2Score(mpath)
+ print "just reading track one for now..."
+
+
+
+
+
+
+
+
+
+
diff --git a/City/__init__.py b/City/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/City/__init__.py
diff --git a/City/midiImport.py b/City/midiImport.py
new file mode 100755
index 0000000..2473343
--- /dev/null
+++ b/City/midiImport.py
@@ -0,0 +1,379 @@
+#!/usr/bin/env python
+"""
+Will Ware
+Dec 6 2001, 10:41 pm
+http://groups.google.com/group/alt.sources/msg/0c5fc523e050c35e
+
+midi.py -- MIDI classes and parser in Python
+Placed into the public domain in December 2001 by Will Ware
+Python MIDI classes: meaningful data structures that represent MIDI events
+and other objects. You can read MIDI files to create such objects, or
+generate a collection of objects and use them to write a MIDI file.
+Helpful MIDI info:
+http://crystal.apana.org.au/ghansper/midi_introduction/midi_file_form...
+http://www.argonet.co.uk/users/lenny/midi/mfile.html
+"""
+import sys, string, types, exceptions
+debugflag = 0
+
+def showstr(str, n=16):
+ for x in str[:n]:
+ print ('%02x' % ord(x)),
+ print
+
+def getNumber(str, length):
+ # MIDI uses big-endian for everything
+ sum = 0
+ for i in range(length):
+ sum = (sum << 8) + ord(str[i])
+ return sum, str[length:]
+
+def getVariableLengthNumber(str):
+ sum = 0
+ i = 0
+ while 1:
+ x = ord(str[i])
+ i = i + 1
+ sum = (sum << 7) + (x & 0x7F)
+ if not (x & 0x80):
+ return sum, str[i:]
+
+def putNumber(num, length):
+ # MIDI uses big-endian for everything
+ lst = [ ]
+ for i in range(length):
+ n = 8 * (length - 1 - i)
+ lst.append(chr((num >> n) & 0xFF))
+ return string.join(lst, "")
+
+def putVariableLengthNumber(x):
+ lst = [ ]
+ while 1:
+ y, x = x & 0x7F, x >> 7
+ lst.append(chr(y + 0x80))
+ if x == 0:
+ break
+ lst.reverse()
+ lst[-1] = chr(ord(lst[-1]) & 0x7f)
+ return string.join(lst, "")
+
+class EnumException(exceptions.Exception):
+ pass
+
+class Enumeration:
+ def __init__(self, enumList):
+ lookup = { }
+ reverseLookup = { }
+ i = 0
+ uniqueNames = [ ]
+ uniqueValues = [ ]
+ for x in enumList:
+ if type(x) == types.TupleType:
+ x, i = x
+ if type(x) != types.StringType:
+ raise EnumException, "enum name is not a string: " + x
+ if type(i) != types.IntType:
+ raise EnumException, "enum value is not an integer: " + i
+ if x in uniqueNames:
+ raise EnumException, "enum name is not unique: " + x
+ if i in uniqueValues:
+ raise EnumException, "enum value is not unique for " + x
+ uniqueNames.append(x)
+ uniqueValues.append(i)
+ lookup[x] = i
+ reverseLookup[i] = x
+ i = i + 1
+ self.lookup = lookup
+ self.reverseLookup = reverseLookup
+ def __add__(self, other):
+ lst = [ ]
+ for k in self.lookup.keys():
+ lst.append((k, self.lookup[k]))
+ for k in other.lookup.keys():
+ lst.append((k, other.lookup[k]))
+ return Enumeration(lst)
+ def hasattr(self, attr):
+ return self.lookup.has_key(attr)
+ def has_value(self, attr):
+ return self.reverseLookup.has_key(attr)
+ def __getattr__(self, attr):
+ if not self.lookup.has_key(attr):
+ raise AttributeError
+ return self.lookup[attr]
+ def whatis(self, value):
+ return self.reverseLookup[value]
+
+channelVoiceMessages = Enumeration([("NOTE_OFF", 0x80),
+ ("NOTE_ON", 0x90),
+ ("POLYPHONIC_KEY_PRESSURE", 0xA0),
+ ("CONTROLLER_CHANGE", 0xB0),
+ ("PROGRAM_CHANGE", 0xC0),
+ ("CHANNEL_KEY_PRESSURE", 0xD0),
+ ("PITCH_BEND", 0xE0)])
+channelModeMessages = Enumeration([("ALL_SOUND_OFF", 0x78),
+ ("RESET_ALL_CONTROLLERS", 0x79),
+ ("LOCAL_CONTROL", 0x7A),
+ ("ALL_NOTES_OFF", 0x7B),
+ ("OMNI_MODE_OFF", 0x7C),
+ ("OMNI_MODE_ON", 0x7D),
+ ("MONO_MODE_ON", 0x7E),
+ ("POLY_MODE_ON", 0x7F)])
+metaEvents = Enumeration([("SEQUENCE_NUMBER", 0x00),
+ ("TEXT_EVENT", 0x01),
+ ("COPYRIGHT_NOTICE", 0x02),
+ ("SEQUENCE_TRACK_NAME", 0x03),
+ ("INSTRUMENT_NAME", 0x04),
+ ("LYRIC", 0x05),
+ ("MARKER", 0x06),
+ ("CUE_POINT", 0x07),
+ ("MIDI_CHANNEL_PREFIX", 0x20),
+ ("MIDI_PORT", 0x21),
+ ("END_OF_TRACK", 0x2F),
+ ("SET_TEMPO", 0x51),
+ ("SMTPE_OFFSET", 0x54),
+ ("TIME_SIGNATURE", 0x58),
+ ("KEY_SIGNATURE", 0x59),
+ ("SEQUENCER_SPECIFIC_META_EVENT", 0x7F)])
+
+# runningStatus appears to want to be an attribute of a MidiTrack. But
+# it doesn't seem to do any harm to implement it as a global.
+runningStatus = None
+class MidiEvent:
+ def __init__(self, track):
+ self.track = track
+ self.time = None
+ self.channel = self.pitch = self.velocity = self.data = None
+ def __cmp__(self, other):
+ # assert self.time != None and other.time != None
+ return cmp(self.time, other.time)
+ def __repr__(self):
+ r = ("<MidiEvent %s, t=%s, track=%s, channel=%s" %
+ (self.type,
+ repr(self.time),
+ self.track.index,
+ repr(self.channel)))
+ for attrib in ["pitch", "data", "velocity"]:
+ if getattr(self, attrib) != None:
+ r = r + ", " + attrib + "=" + repr(getattr(self, attrib))
+ return r + ">"
+ def read(self, time, str):
+ global runningStatus
+ self.time = time
+ # do we need to use running status?
+ if not (ord(str[0]) & 0x80):
+ str = runningStatus + str
+ runningStatus = x = str[0]
+ x = ord(x)
+ y = x & 0xF0
+ z = ord(str[1])
+ if channelVoiceMessages.has_value(y):
+ self.channel = (x & 0x0F) + 1
+ self.type = channelVoiceMessages.whatis(y)
+ if (self.type == "PROGRAM_CHANGE" or
+ self.type == "CHANNEL_KEY_PRESSURE"):
+ self.data = z
+ return str[2:]
+ else:
+ self.pitch = z
+ self.velocity = ord(str[2])
+ channel = self.track.channels[self.channel - 1]
+ if (self.type == "NOTE_OFF" or
+ (self.velocity == 0 and self.type == "NOTE_ON")):
+ channel.noteOff(self.pitch, self.time)
+ elif self.type == "NOTE_ON":
+ channel.noteOn(self.pitch, self.time, self.velocity)
+ return str[3:]
+ elif y == 0xB0 and channelModeMessages.has_value(z):
+ self.channel = (x & 0x0F) + 1
+ self.type = channelModeMessages.whatis(z)
+ if self.type == "LOCAL_CONTROL":
+ self.data = (ord(str[2]) == 0x7F)
+ elif self.type == "MONO_MODE_ON":
+ self.data = ord(str[2])
+ return str[3:]
+ elif x == 0xF0 or x == 0xF7:
+ self.type = {0xF0: "F0_SYSEX_EVENT",
+ 0xF7: "F7_SYSEX_EVENT"}[x]
+ length, str = getVariableLengthNumber(str[1:])
+ self.data = str[:length]
+ return str[length:]
+ elif x == 0xFF:
+ if not metaEvents.has_value(z):
+ print "Unknown meta event: FF %02X" % z
+ sys.stdout.flush()
+ raise "Unknown midi event type"
+ self.type = metaEvents.whatis(z)
+ length, str = getVariableLengthNumber(str[2:])
+ self.data = str[:length]
+ return str[length:]
+ raise "Unknown midi event type"
+ def write(self):
+ sysex_event_dict = {"F0_SYSEX_EVENT": 0xF0,
+ "F7_SYSEX_EVENT": 0xF7}
+ if channelVoiceMessages.hasattr(self.type):
+ x = chr((self.channel - 1) +
+ getattr(channelVoiceMessages, self.type))
+ if (self.type != "PROGRAM_CHANGE" and
+ self.type != "CHANNEL_KEY_PRESSURE"):
+ data = chr(self.pitch) + chr(self.velocity)
+ else:
+ data = chr(self.data)
+ return x + data
+ elif channelModeMessages.hasattr(self.type):
+ x = getattr(channelModeMessages, self.type)
+ x = (chr(0xB0 + (self.channel - 1)) +
+ chr(x) +
+ chr(self.data))
+ return x
+ elif sysex_event_dict.has_key(self.type):
+ str = chr(sysex_event_dict[self.type])
+ str = str + putVariableLengthNumber(len(self.data))
+ return str + self.data
+ elif metaEvents.hasattr(self.type):
+ str = chr(0xFF) + chr(getattr(metaEvents, self.type))
+ str = str + putVariableLengthNumber(len(self.data))
+ return str + self.data
+ else:
+ raise "unknown midi event type: " + self.type
+
+"""
+register_note() is a hook that can be overloaded from a script that
+imports this module. Here is how you might do that, if you wanted to
+store the notes as tuples in a list. Including the distinction
+between track and channel offers more flexibility in assigning voices.
+import midi
+notelist = [ ]
+def register_note(t, c, p, v, t1, t2):
+ notelist.append((t, c, p, v, t1, t2))
+midi.register_note = register_note
+"""
+def register_note(track_index, channel_index, pitch, velocity,
+ keyDownTime, keyUpTime):
+ pass
+
+class MidiChannel:
+ """A channel (together with a track) provides the continuity connecting
+ a NOTE_ON event with its corresponding NOTE_OFF event. Together, those
+ define the beginning and ending times for a Note."""
+ def __init__(self, track, index):
+ self.index = index
+ self.track = track
+ self.pitches = { }
+ def __repr__(self):
+ return "<MIDI channel %d>" % self.index
+ def noteOn(self, pitch, time, velocity):
+ self.pitches[pitch] = (time, velocity)
+ def noteOff(self, pitch, time):
+ if self.pitches.has_key(pitch):
+ keyDownTime, velocity = self.pitches[pitch]
+ register_note(self.track.index, self.index, pitch, velocity,
+ keyDownTime, time)
+ del self.pitches[pitch]
+ # The case where the pitch isn't in the dictionary is illegal,
+ # I think, but we probably better just ignore it.
+
+class DeltaTime(MidiEvent):
+ type = "DeltaTime"
+ def read(self, oldstr):
+ self.time, newstr = getVariableLengthNumber(oldstr)
+ return self.time, newstr
+ def write(self):
+ str = putVariableLengthNumber(self.time)
+ return str
+
+class MidiTrack:
+ def __init__(self, index):
+ self.index = index
+ self.events = [ ]
+ self.channels = [ ]
+ self.length = 0
+ for i in range(16):
+ self.channels.append(MidiChannel(self, i+1))
+ def read(self, str):
+ time = 0
+ assert str[:4] == "MTrk"
+ length, str = getNumber(str[4:], 4)
+ self.length = length
+ mystr = str[:length]
+ remainder = str[length:]
+ while mystr:
+ delta_t = DeltaTime(self)
+ dt, mystr = delta_t.read(mystr)
+ time = time + dt
+ self.events.append(delta_t)
+ e = MidiEvent(self)
+ mystr = e.read(time, mystr)
+ self.events.append(e)
+ return remainder
+ def write(self):
+ time = self.events[0].time
+ # build str using MidiEvents
+ str = ""
+ for e in self.events:
+ str = str + e.write()
+ return "MTrk" + putNumber(len(str), 4) + str
+ def __repr__(self):
+ r = "<MidiTrack %d -- %d events\\n" % (self.index, len(self.events))
+ for e in self.events:
+ r = r + " " + `e` + "\\n"
+ return r + " >"
+
+class MidiFile:
+ def __init__(self):
+ self.file = None
+ self.format = 1
+ self.tracks = [ ]
+ self.ticksPerQuarterNote = None
+ self.ticksPerSecond = None
+ def open(self, filename, attrib="rb"):
+ if filename == None:
+ if attrib in ["r", "rb"]:
+ self.file = sys.stdin
+ else:
+ self.file = sys.stdout
+ else:
+ self.file = open(filename, attrib)
+ def __repr__(self):
+ r = "<MidiFile %d tracks\\n" % len(self.tracks)
+ for t in self.tracks:
+ r = r + " " + `t` + "\\n"
+ return r + ">"
+ def close(self):
+ self.file.close()
+ def read(self):
+ self.readstr(self.file.read())
+ def readstr(self, str):
+ assert str[:4] == "MThd"
+ length, str = getNumber(str[4:], 4)
+ assert length == 6
+ format, str = getNumber(str, 2)
+ self.format = format
+ assert format == 0 or format == 1 # dunno how to handle 2
+ numTracks, str = getNumber(str, 2)
+ division, str = getNumber(str, 2)
+ if division & 0x8000:
+ framesPerSecond = -((division >> 8) | -128)
+ ticksPerFrame = division & 0xFF
+ assert ticksPerFrame == 24 or ticksPerFrame == 25 or \
+ ticksPerFrame == 29 or ticksPerFrame == 30
+ if ticksPerFrame == 29: ticksPerFrame = 30 # drop frame
+ self.ticksPerSecond = ticksPerFrame * framesPerSecond
+ else:
+ self.ticksPerQuarterNote = division & 0x7FFF
+ for i in range(numTracks):
+ trk = MidiTrack(i)
+ str = trk.read(str)
+ self.tracks.append(trk)
+ def write(self):
+ self.file.write(self.writestr())
+ def writestr(self):
+ division = self.ticksPerQuarterNote
+ # Don't handle ticksPerSecond yet, too confusing
+ assert (division & 0x8000) == 0
+ str = "MThd" + putNumber(6, 4) + putNumber(self.format, 2)
+ str = str + putNumber(len(self.tracks), 2)
+ str = str + putNumber(division, 2)
+ for trk in self.tracks:
+ str = str + trk.write()
+ return str
+
diff --git a/City/setup.py b/City/setup.py
new file mode 100755
index 0000000..94950e1
--- /dev/null
+++ b/City/setup.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+from sugar.activity import bundlebuilder
+if __name__ == "__main__":
+ bundlebuilder.start("City")
diff --git a/InstrumentPanel.py b/InstrumentPanel.py
new file mode 100644
index 0000000..03f0110
--- /dev/null
+++ b/InstrumentPanel.py
@@ -0,0 +1,19 @@
+import gtk
+from sugar.graphics import style
+
+class InstrumentPanel(gtk.EventBox):
+ def __init__(self):
+ gtk.EventBox.__init__(self)
+ self.Box = gtk.VBox()
+ self.status_label = gtk.Label()
+ self.Box.pack_start(self.status_label, True, True, 10)
+ self.score_label = gtk.Label()
+ self.Box.pack_start(self.score_label, True, True, 10)
+ self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("dark grey"))
+ self.add(self.Box)
+ self.show_all()
+
+ def show(self, text):
+ self.status_label.set_text(text)
+
+ \ No newline at end of file
diff --git a/J2JToolbar.py b/J2JToolbar.py
new file mode 100755
index 0000000..550fa78
--- /dev/null
+++ b/J2JToolbar.py
@@ -0,0 +1,301 @@
+import gtk, gst, thread, tempfile, time, os
+import pygame
+import olpcgames
+from gettext import gettext as _
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.toolcombobox import ToolComboBox
+import logging
+
+log = logging.getLogger('City run')
+log.setLevel(logging.DEBUG)
+
+ImagePath = os.path.dirname(os.path.abspath(__file__)) + "/City/Images"
+
+GST_PIPE = ['v4l2src', 'ffmpegcolorspace', 'pngenc']
+
+class readScenes(object):
+ def __init__(self, scpath):
+ self.scpath = scpath
+ self.scene_names = [dir for dir in os.listdir(self.scpath) if os.path.isdir(self.scpath + '/' + dir)]
+ self.scene_data = []
+ for n in self.scene_names:
+ fp = self.scpath + "/" + n
+ mdfile = [open(fp + '/' + f) for f in os.listdir(fp) if os.path.isfile(fp + "/" + f) and f.startswith(n)]
+ if mdfile:
+ result = {"Name":n}
+ defaults = {}
+ for line in mdfile[0]:
+ if line.startswith('#') or line.startswith('\n'):
+ pass
+ else:
+ keyvals = line.split('=')
+ if len(keyvals) == 2:
+ key = keyvals[0].upper()
+ val = (keyvals[1][:-1] if keyvals[1][-1] == '\n' else keyvals[1])
+ if key.startswith('TEMPO'):
+ result['Tempo'] = val.replace(' ','')
+ elif key.startswith('KEY'):
+ result['Key'] = val.replace(' ','')
+ elif key.startswith('MODE'):
+ result['Mode'] = val.replace(' ','')
+ else:
+ pass
+ else:
+ raise IOError, "Bad Scene Meta Data file: %s" %keyvals
+ result['Defaults'] = {}
+ self.scene_data.append(result)
+ else:
+ raise IOError, "Can't find Meta Data file in %s Scene" %n
+ def scene_instruct(self, name):
+ "returns a list of strings suitable to give to a ScenePlayer object for creating a scene"
+ for scd in self.scene_data:
+ if scd['Name'] == name:
+ collected = [name]
+ for k in ['Key', 'Mode', 'Tempo', 'Defaults']:
+ try:
+ collected.append(str(scd[k]))
+ except KeyError:
+ collected.append('None')
+ return collected
+ def get_scene_list(self):
+ "returns a list of scene strings for the toolbar, with City as the default"
+ ordered_names = self.scene_names[:]
+ if 'City' in ordered_names:
+ ordered_names.insert(0,ordered_names.pop(ordered_names.index('City')))
+ return [self.scene_instruct(s) for s in ordered_names]
+
+class CameraSnap(object):
+ """A class representing the OLPC camera."""
+ def __init__(self):
+ log.info("CameraSnap init")
+ snap_file, self.snap_path = tempfile.mkstemp(suffix = '.png')
+ pipe = GST_PIPE + ['filesink location=%s' % self.snap_path]
+ self.pipe = gst.parse_launch('!'.join(pipe))
+ self.bus = self.pipe.get_bus()
+ log.info("tempfile is %s " %self.snap_path)
+ def Snap(self):
+ """Take a snapshot."""
+ log.info("about to set pipe state to PLAY")
+ self.pipe.set_state(gst.STATE_PLAYING)
+ log.info("about to poll")
+ thread.start_new_thread(self.bus.poll, (gst.MESSAGE_EOS, -1))
+ for i in xrange(60):
+ time.sleep(0.18)
+ if os.path.getsize(self.snap_path) > 0: break
+ else: raise IOError, "Error writing camera snap to file"
+ return self.snap_path
+ def Stop(self):
+ self.pipe.set_state(gst.STATE_NULL)
+
+
+class Jam2JamToolBar(gtk.Toolbar):
+ def __init__(self, activity):
+ gtk.Toolbar.__init__(self)
+ self.activity = activity
+ self.parameters = ['Density', 'Pitch', 'Length', 'Timbre', 'Volume'] # no tempo here.
+ scene_stuff = readScenes(self.activity._ScenePath)
+ self.scenes = scene_stuff.get_scene_list()
+ print "SCENE DATA IS ", self.scenes
+ #self.scenes = [['City', 'A', 'minor pentatonic'], ['City', 'G#', 'major']] #this data needs to be obtained from directories
+ self.play_pause_state = 'Playing'
+ self.scene_init = True
+
+ # Separator
+ separator = gtk.SeparatorToolItem()
+ separator.set_draw(True)
+ self.insert(separator, -1)
+
+ #Horizontal Parameter control combobox
+ self._add_widget(gtk.Label(_('Horizontal:')))
+ self._Hparameter_combo = ToolComboBox()
+ for i, f in enumerate(self.parameters):
+ self._Hparameter_combo.combo.append_item(i, f)
+ self._Hparameter_combo.combo.connect('changed', self._Hparameter_change_cb)
+ self._add_widget(self._Hparameter_combo)
+ self._Hparameter_combo.combo.set_active(0)
+
+ # Separator
+ separator = gtk.SeparatorToolItem()
+ separator.set_draw(True)
+ separator.show()
+ self.insert(separator, -1)
+
+ #Vertical Parameter control combobox
+ self._add_widget(gtk.Label(_('Vertical:')))
+ self._Vparameter_combo = ToolComboBox()
+ for j, k in enumerate(self.parameters):
+ self._Vparameter_combo.combo.append_item(j, k)
+ self._Vparameter_combo.combo.connect('changed', self._Vparameter_change_cb)
+ self._add_widget(self._Vparameter_combo)
+ self._Vparameter_combo.combo.set_active(1)
+
+ # Separator
+ separator = gtk.SeparatorToolItem()
+ separator.set_draw(True)
+ separator.show()
+ self.insert(separator, -1)
+
+
+ #Scene Selection control combobox
+ self._add_widget(gtk.Label(_('Scene:')))
+ self._Scene_combo = ToolComboBox()
+ for l, m in enumerate(self.scenes):
+ self._Scene_combo.combo.append_item(l, m[0])
+ self._Scene_combo.combo.connect('changed', self._Scene_change_cb)
+ self._add_widget(self._Scene_combo)
+ #ought to do this safely somehow.
+ self._Scene_combo.combo.set_active(0)
+ self.scene_init = False
+
+ # Separator
+ separator = gtk.SeparatorToolItem()
+ separator.set_draw(True)
+ separator.show()
+ self.insert(separator, -1)
+
+
+ #Camera Button
+ self.camera_ready = True
+ camera_icon = ImagePath + "/camera-external.svg"
+ camera_busy_icon = ImagePath + "/camera-busy.svg"
+ self.camera_image, self.camera_busy_image = gtk.Image(), gtk.Image()
+ self.camera_image.set_from_file(camera_icon)
+ self.camera_busy_image.set_from_file(camera_busy_icon)
+ self.camera_image.show()
+ #camera_busy_image.show()
+ self._cameraButton = ToolButton()
+ self._cameraButton.set_icon_widget(self.camera_image)
+ self._cameraButton.connect('clicked', self._cameraSnap_cb)
+ self._cameraButton.set_tooltip(_('Snapshot'))
+ self.insert(self._cameraButton, -1)
+ self._cameraButton.show()
+
+ # Separator
+ separator = gtk.SeparatorToolItem()
+ separator.set_draw(True)
+ separator.show()
+ self.insert(separator, -1)
+
+ #Play/Pause Button
+ pause_icon = ImagePath + "/media-playback-pause.svg"
+ play_icon = ImagePath + "/media-playback-start.svg"
+ self.pause_image = gtk.Image()
+ self.pause_image.set_from_file(pause_icon)
+
+ self.play_image = gtk.Image()
+ self.play_image.set_from_file(play_icon)
+
+ self._pauseButton = ToolButton()
+ self._pauseButton.connect('clicked', self._pause_cb)
+ self.pause_image.show()
+ self._pauseButton.set_icon_widget(self.pause_image)
+ self._pauseButton.set_tooltip(_('Pause'))
+ #self._toggleplay_pause()
+ self.insert(self._pauseButton, -1)
+ self._pauseButton.show()
+
+ # Separator
+ separator = gtk.SeparatorToolItem()
+ separator.set_draw(True)
+ separator.show()
+ self.insert(separator, -1)
+
+
+ def _add_widget(self, widget, expand=False):
+ tool_item = gtk.ToolItem()
+ tool_item.set_expand(expand)
+ tool_item.add(widget)
+ widget.show()
+ self.insert(tool_item, -1)
+ tool_item.show()
+
+ def _toggleplay_pause(self):
+ if self.play_pause_state == "Playing":
+ self.activity.jamScene.music_player.pause()
+ self.play_image.show()
+ self._pauseButton.set_icon_widget(self.play_image)
+ self._pauseButton.set_tooltip(_('Play'))
+ self.play_pause_state = "Paused"
+ else:
+ self.activity.jamScene.music_player.resume()
+ self.pause_image.show()
+ self._pauseButton.set_icon_widget(self.pause_image)
+ self._pauseButton.set_tooltip(_('Pause'))
+ self.play_pause_state = "Playing"
+ try:
+ self.activity._pgc.grab_focus()
+ except AttributeError:
+ pass
+
+ def _show_busy_camera(self):
+ self.camera_ready = False
+ self.camera_busy_image.show()
+ self._cameraButton.set_icon_widget(self.camera_busy_image)
+ self._cameraButton.set_tooltip(_('Please wait...'))
+
+ def _show_active_camera(self):
+ self.camera_image.show()
+ self._cameraButton.set_icon_widget(self.camera_image)
+ self._cameraButton.set_tooltip(_('Snap'))
+ self.camera_ready = True
+
+ def _Hparameter_change_cb(self, widget):
+ param = "Parameter|Horizontal|" + self.parameters[self._Hparameter_combo.combo.get_active()]
+ olpcgames.eventwrap.post(olpcgames.eventwrap.Event(pygame.USEREVENT, action=param))
+ try:
+ self.activity._pgc.grab_focus()
+ except AttributeError:
+ pass
+
+ def _Vparameter_change_cb(self, widget):
+ param = "Parameter|Vertical|" + self.parameters[self._Vparameter_combo.combo.get_active()]
+ olpcgames.eventwrap.post(olpcgames.eventwrap.Event(pygame.USEREVENT, action=param))
+ try:
+ self.activity._pgc.grab_focus()
+ except AttributeError:
+ pass
+
+ def _Scene_change_cb(self, widget):
+ if self.scene_init:
+ pass
+ else:
+ selection = self.scenes[self._Scene_combo.combo.get_active()]
+ scene = "Reload|" + '|'.join(map(lambda x: str(x), selection))
+ olpcgames.eventwrap.post(olpcgames.eventwrap.Event(pygame.USEREVENT, action=scene))
+ try:
+ self.activity._pgc.grab_focus()
+ except AttributeError:
+ pass
+
+ ### functions to assist calls from pygame
+ def deactivate_scene_change(self):
+ self._Scene_combo.set_sensitive(False)
+ def reactivate_scene_change(self):
+ self._Scene_combo.set_sensitive(True)
+ def set_horizontal_parameter(self, param):
+ ndx = self.parameters.index(param)
+ self._Hparameter_combo.combo.set_active(ndx)
+ def set_vertical_parameter(self, param):
+ ndx = self.parameters.index(param)
+ self._Vparameter_combo.combo.set_active(ndx)
+
+ def _cameraSnap_cb(self, widget):
+ "Here I could wrap a camera event..."
+ def snaptime():
+ snap = CameraSnap()
+ self.activity.cameras_loaded.append(snap)
+ picpath = snap.Snap()
+ self.activity.load_image(picpath)
+ snap.Stop()
+ self._show_active_camera()
+ self.activity._pgc.grab_focus()
+ if self.camera_ready:
+ self._show_busy_camera()
+ thread.start_new_thread(snaptime, ())
+ else:
+ log.info('Ignoring request to use camera, as camera is currently busy')
+
+ def _pause_cb(self, widget):
+ self._toggleplay_pause()
+ log.info("Play/Pause Button pressed")
+
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..c91c5a1
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,175 @@
+activity.py
+InstrumentPanel.py
+J2JToolbar.py
+run.py
+setup.py
+arrow40b-mask.xbm
+arrow40b.xbm
+MANIFEST.in
+POTFILES.in
+NEWS
+README
+COPYING
+activity/activity.svg
+activity/activity.info
+City/__init__.py
+City/City.py
+City/CsHelpers.py
+City/CsSched.py
+City/midiImport.py
+City/OrcBuilder.py
+City/Parameters.py
+City/setup.py
+City/Tracks.py
+City/Scenes/Reggae/AudioFiles/LeadAudio/B2_guitar.aif
+City/Scenes/Reggae/AudioFiles/LeadAudio/B3_guitar.aif
+City/Scenes/Reggae/AudioFiles/LeadAudio/E2_guitar.aif
+City/Scenes/Reggae/AudioFiles/LeadAudio/E3_guitar.aif
+City/Scenes/Reggae/AudioFiles/LeadAudio/E4_guitar.aif
+City/Scenes/Reggae/AudioFiles/LeadAudio/E5_guitar.aif
+City/Scenes/Reggae/AudioFiles/DrumsAudio/ClosedhatF#1.aif
+City/Scenes/Reggae/AudioFiles/DrumsAudio/Kick-C1.aif
+City/Scenes/Reggae/AudioFiles/DrumsAudio/OpenhatA#1.aif
+City/Scenes/Reggae/AudioFiles/DrumsAudio/Snare-D1.aif
+City/Scenes/Reggae/AudioFiles/DrumsAudio/TambF#2.aif
+City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoC4[70-72-73].aif
+City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoC5[82-84-90].aif
+City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoE3[63-64-65].aif
+City/Scenes/Reggae/AudioFiles/ChordsAudio/epnoE4[74-76-77].aif
+City/Scenes/Reggae/AudioFiles/BassAudio/BassC2[41-48-50].aif
+City/Scenes/Reggae/AudioFiles/BassAudio/BassC3[59-60-62].aif
+City/Scenes/Reggae/AudioFiles/BassAudio/BassC4[70-72-78].aif
+City/Scenes/Reggae/AudioFiles/BassAudio/BassF#2[51-54-58].aif
+City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets A4 22 16 mono.aif
+City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets C4 22 16 mono.aif
+City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets E3 22 16 mono.aif
+City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets F4 22 16 mono.aif
+City/Scenes/Latin/AudioFiles/LeadAudio/Trumpets G3 22 16 mono.aif
+City/Scenes/Latin/AudioFiles/DrumsAudio/Bongo C#3 22 16 Mono.aif
+City/Scenes/Latin/AudioFiles/DrumsAudio/Bongo C3 22 16 Mono.aif
+City/Scenes/Latin/AudioFiles/DrumsAudio/Bongo D3 22 16 Mono.aif
+City/Scenes/Latin/AudioFiles/DrumsAudio/ClosedhatF#1.aif
+City/Scenes/Latin/AudioFiles/DrumsAudio/Kick-C1.aif
+City/Scenes/Latin/AudioFiles/DrumsAudio/Shaker 22 16 Mono.aif
+City/Scenes/Latin/AudioFiles/DrumsAudio/Snare-D1.aif
+City/Scenes/Latin/AudioFiles/DrumsAudio/TambF#2.aif
+City/Scenes/Latin/AudioFiles/ChordsAudio/Organ C4 22 16 mono.aif
+City/Scenes/Latin/AudioFiles/ChordsAudio/Organ C5 22 16 mono.aif
+City/Scenes/Latin/AudioFiles/ChordsAudio/Organ G3 22 16 mono.aif
+City/Scenes/Latin/AudioFiles/ChordsAudio/Organ G4 22 16 mono.aif
+City/Scenes/Latin/AudioFiles/BassAudio/BassC2[41-48-50].aif
+City/Scenes/Latin/AudioFiles/BassAudio/BassC3[59-60-62].aif
+City/Scenes/Latin/AudioFiles/BassAudio/BassC4[70-72-78].aif
+City/Scenes/Latin/AudioFiles/BassAudio/BassF#2[51-54-58].aif
+City/Scenes/Country/AudioFiles/LeadAudio/B2_guitar.aif
+City/Scenes/Country/AudioFiles/LeadAudio/B3_guitar.aif
+City/Scenes/Country/AudioFiles/LeadAudio/E2_guitar.aif
+City/Scenes/Country/AudioFiles/LeadAudio/E3_guitar.aif
+City/Scenes/Country/AudioFiles/LeadAudio/E4_guitar.aif
+City/Scenes/Country/AudioFiles/LeadAudio/E5_guitar.aif
+City/Scenes/Country/AudioFiles/DrumsAudio/C#1_rim_shot.aif
+City/Scenes/Country/AudioFiles/DrumsAudio/C1_kick_drum.aif
+City/Scenes/Country/AudioFiles/DrumsAudio/D1_snare_drum.aif
+City/Scenes/Country/AudioFiles/DrumsAudio/F#1_closed_hi_hat.aif
+City/Scenes/Country/AudioFiles/ChordsAudio/epnoC4[70-72-73].aif
+City/Scenes/Country/AudioFiles/ChordsAudio/epnoC5[82-84-90].aif
+City/Scenes/Country/AudioFiles/ChordsAudio/epnoE3[63-64-65].aif
+City/Scenes/Country/AudioFiles/ChordsAudio/epnoE4[74-76-77].aif
+City/Scenes/Country/AudioFiles/BassAudio/BassC2[41-48-50].aif
+City/Scenes/Country/AudioFiles/BassAudio/BassC3[59-60-62].aif
+City/Scenes/Country/AudioFiles/BassAudio/BassC4[70-72-78].aif
+City/Scenes/Country/AudioFiles/BassAudio/BassF#2[51-54-58].aif
+City/Scenes/City/AudioFiles/LeadAudio/keys-good[28-36-39]_001_001mn_000.aif
+City/Scenes/City/AudioFiles/LeadAudio/keys-good[40-48-51]_001_001mn_000.aif
+City/Scenes/City/AudioFiles/LeadAudio/keys-good[52-60-63]_001_001mn_000.aif
+City/Scenes/City/AudioFiles/LeadAudio/keys-good[64-72-75]_001_001mn_000.aif
+City/Scenes/City/AudioFiles/LeadAudio/keys-good[76-84-87]_001_001mn_000.aif
+City/Scenes/City/AudioFiles/LeadAudio/keys-good[88-96-99]_001_001mn_000.aif
+City/Scenes/City/AudioFiles/DrumsAudio/Dance1909kick7[36-36-36].aif
+City/Scenes/City/AudioFiles/DrumsAudio/Dance1909snare3[38-38-38].aif
+City/Scenes/City/AudioFiles/DrumsAudio/Dance1closedhat11[39-39-39].aif
+City/Scenes/City/AudioFiles/DrumsAudio/Dance1openedhat4[42-42-42].aif
+City/Scenes/City/AudioFiles/DrumsAudio/Dance1SHAKER_1[46-46-46].aif
+City/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[100-108-111]mn_000.aif
+City/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[64-72-75]mn_000.aif
+City/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[76-84-87]mn_000.aif
+City/Scenes/City/AudioFiles/ChordsAudio/Dance1Pad[88-96-99]mn_000.aif
+City/Scenes/City/AudioFiles/BassAudio/Dance1Bass[28-36-39]mn_000.aif
+City/Scenes/City/AudioFiles/BassAudio/Dance1Bass[40-48-51]mn_000.aif
+City/Scenes/City/AudioFiles/BassAudio/Dance1Bass[52-60-63]mn_000.aif
+City/Scenes/Blues/AudioFiles/LeadAudio/B2_guitar.aif
+City/Scenes/Blues/AudioFiles/LeadAudio/B3_guitar.aif
+City/Scenes/Blues/AudioFiles/LeadAudio/E2_guitar.aif
+City/Scenes/Blues/AudioFiles/LeadAudio/E3_guitar.aif
+City/Scenes/Blues/AudioFiles/LeadAudio/E4_guitar.aif
+City/Scenes/Blues/AudioFiles/LeadAudio/E5_guitar.aif
+City/Scenes/Blues/AudioFiles/DrumsAudio/C1_kick_drum.aif
+City/Scenes/Blues/AudioFiles/DrumsAudio/D1_snare_drum.aif
+City/Scenes/Blues/AudioFiles/DrumsAudio/F#1_closed_hi_hat.aif
+City/Scenes/Blues/AudioFiles/DrumsAudio/Tamborine 22 16 Mono.aif
+City/Scenes/Blues/AudioFiles/ChordsAudio/epnoC4[70-72-73].aif
+City/Scenes/Blues/AudioFiles/ChordsAudio/epnoC5[82-84-90].aif
+City/Scenes/Blues/AudioFiles/ChordsAudio/epnoE3[63-64-65].aif
+City/Scenes/Blues/AudioFiles/ChordsAudio/epnoE4[74-76-77].aif
+City/Scenes/Blues/AudioFiles/BassAudio/BassC2[41-48-50].aif
+City/Scenes/Blues/AudioFiles/BassAudio/BassC3[59-60-62].aif
+City/Scenes/Blues/AudioFiles/BassAudio/BassC4[70-72-78].aif
+City/Scenes/Blues/AudioFiles/BassAudio/BassF#2[51-54-58].aif
+City/Scenes/Reggae/MidiFiles/Bass.mid
+City/Scenes/Reggae/MidiFiles/Chords.mid
+City/Scenes/Reggae/MidiFiles/Drums.mid
+City/Scenes/Reggae/MidiFiles/Lead.mid
+City/Scenes/Latin/MidiFiles/Bass.mid
+City/Scenes/Latin/MidiFiles/Chords.mid
+City/Scenes/Latin/MidiFiles/Drums.mid
+City/Scenes/Latin/MidiFiles/Lead.mid
+City/Scenes/Country/MidiFiles/Bass.mid
+City/Scenes/Country/MidiFiles/Chords.mid
+City/Scenes/Country/MidiFiles/Drums.mid
+City/Scenes/Country/MidiFiles/Lead.mid
+City/Scenes/City/MidiFiles/Bass.mid
+City/Scenes/City/MidiFiles/Chords.mid
+City/Scenes/City/MidiFiles/Drums.mid
+City/Scenes/City/MidiFiles/Lead.mid
+City/Scenes/Blues/MidiFiles/Bass.mid
+City/Scenes/Blues/MidiFiles/Chords.mid
+City/Scenes/Blues/MidiFiles/Drums.mid
+City/Scenes/Blues/MidiFiles/Lead.mid
+City/Images/Bass.png
+City/Images/Chords.png
+City/Images/Drums.png
+City/Images/jam2jamXO_2.png
+City/Images/Lead.png
+City/Images/Mic.png
+City/Images/Video.png
+City/Images/camera-busy.svg
+City/Images/camera-external.svg
+City/Images/media-playback-pause.svg
+City/Images/media-playback-start.svg
+City/Scenes/Reggae/Reggae_Data.txt
+City/Scenes/Latin/Latin_Data.txt
+City/Scenes/Country/Country_Data.txt
+City/Scenes/City/City_Data.txt
+City/Scenes/Blues/Blues_Data.txt
+po/POTFILES.in
+olpcgames/COPYING
+olpcgames/__init__.py
+olpcgames/_cairoimage.py
+olpcgames/_gtkmain.py
+olpcgames/_version.py
+olpcgames/activity.py
+olpcgames/buildmanifest.py
+olpcgames/camera.py
+olpcgames/canvas.py
+olpcgames/dbusproxy.py
+olpcgames/eventwrap.py
+olpcgames/gtkEvent.py
+olpcgames/mesh.py
+olpcgames/pangofont.py
+olpcgames/pausescreen.py
+olpcgames/svgsprite.py
+olpcgames/textsprite.py
+olpcgames/util.py
+olpcgames/video.py
+olpcgames/data/__init__.py
+olpcgames/data/sleeping_svg.py \ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100755
index 0000000..5b14813
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,6 @@
+include *.py *.xbm *.in NEWS README COPYING
+recursive-include activity *.svg *.info
+recursive-include City *.py *.aif *.mid *.png *.svg *.txt
+include po/POTFILES.in
+include olpcgames/COPYING
+recursive-include olpcgames *.py
diff --git a/NEWS b/NEWS
new file mode 100755
index 0000000..12c86ba
--- /dev/null
+++ b/NEWS
@@ -0,0 +1 @@
+There is no news :) \ No newline at end of file
diff --git a/POTFILES.in b/POTFILES.in
new file mode 100755
index 0000000..6c9e6b4
--- /dev/null
+++ b/POTFILES.in
@@ -0,0 +1 @@
+include *.py
diff --git a/README b/README
new file mode 100644
index 0000000..840bff8
--- /dev/null
+++ b/README
@@ -0,0 +1,9 @@
+Jam2Jam activity for the OLPC
+
+The jam2jam XO software was designed and developed as part of the
+Network Jamming research project supported by the Australasian CRC for
+Interaction Design (ACID) in 2009; http://acid.net.au. The jam2jam XO
+team are based in Brisbane, Australia and consist of Steve Dillon,
+Thorin Kerr, and Andrew R. Brown.
+
+Copyright under the GNU GPL
diff --git a/activity.py b/activity.py
new file mode 100755
index 0000000..bba8d80
--- /dev/null
+++ b/activity.py
@@ -0,0 +1,86 @@
+import pygame
+import olpcgames
+from sugar.graphics.toolbutton import ToolButton
+import sugar.activity
+from sugar.activity.activity import Activity, ActivityToolbox
+from olpcgames import activity
+from J2JToolbar import Jam2JamToolBar
+
+from gettext import gettext as _
+import logging, os
+log = logging.getLogger( 'City run' )
+log.setLevel( logging.DEBUG )
+
+log.info( """ LOG From activity.py!!""")
+
+from olpcgames import mesh, util
+
+class Activity(activity.PyGameActivity):
+ """Your Sugar activity"""
+ game_name = 'run:main'
+ game_title = _('Jam2Jam')
+ game_size = None
+ _ScenePath = (os.path.dirname(os.path.abspath(__file__)) + "/City/Scenes")
+
+ def __init__(self, handle):
+ activity.PyGameActivity.__init__(self, handle)
+ self.snap_store = []
+ self.cameras_loaded = []
+ self.playArea = None
+ self.jamScene = None
+
+ def load_image(self, picpath):
+ picsurf = pygame.image.load(picpath)
+ picsurf = picsurf.convert()
+ picsurfwidth = picsurf.get_width()
+ destwidth = self.playArea.width
+ scale = float(destwidth) / picsurfwidth
+ newarea = (picsurfwidth * scale, picsurf.get_height() * scale)
+ picsurf = pygame.transform.scale(picsurf, newarea)
+ self.snap_store.append(picsurf)
+
+
+ def build_toolbar(self):
+ log.info ("building toolbar")
+ toolbox = ActivityToolbox(self)
+ #remove the 'keep' button. We've no need to save data at the moment.
+ activityToolbar = toolbox.get_activity_toolbar()
+ activityToolbar.keep.props.visible = False
+ self.J2JToolbar = Jam2JamToolBar(self)
+ toolbox.add_toolbar("Transform", self.J2JToolbar)
+ self.set_toolbox(toolbox)
+ self.J2JToolbar.show()
+ toolbox.show()
+ self.toolbox.set_current_toolbar(1)
+ def shared_cb(*args, **kwargs):
+ log.info( 'Shared CB: %s, %s', args, kwargs )
+ try:
+ mesh.activity_shared(self)
+ except Exception, err:
+ log.error( """Failure signaling activity sharing to mesh module: %s""", util.get_traceback(err) )
+ else:
+ log.info( 'mesh activity shared message sent, trying to grab focus' )
+ try:
+ self._pgc.grab_focus()
+ except Exception, err:
+ log.warn( 'Focus failed: %s', err )
+ else:
+ log.info( 'asserting focus' )
+ assert self._pgc.is_focus(), """Did not successfully set pygame canvas focus"""
+ sharermessage = "Shared:StartBeat"
+ olpcgames.eventwrap.post(olpcgames.eventwrap.Event(pygame.USEREVENT, action=sharermessage))
+ log.info( 'callback finished' )
+ def joined_cb(*args, **kwargs):
+ log.info( 'joined CB: %s, %s', args, kwargs )
+ mesh.activity_joined(self)
+ self._pgc.grab_focus()
+ joinedmessage = "Joined:CeasePlayer"
+ olpcgames.eventwrap.post(olpcgames.eventwrap.Event(pygame.USEREVENT, action=joinedmessage))
+ self.connect("shared", shared_cb)
+ self.connect("joined", joined_cb)
+ if self.get_shared():
+ joined_cb()
+ log.info ("FINISHED building toolbar")
+ return toolbox
+
+
diff --git a/activity/activity.info b/activity/activity.info
new file mode 100755
index 0000000..3ebc20c
--- /dev/null
+++ b/activity/activity.info
@@ -0,0 +1,7 @@
+[Activity]
+name = Jam2Jam
+activity_version = 1
+host_version = 1
+bundle_id = au.net.acid.Jam2Jam
+icon = activity
+exec = sugar-activity activity.Activity
diff --git a/activity/activity.svg b/activity/activity.svg
new file mode 100755
index 0000000..628d3fe
--- /dev/null
+++ b/activity/activity.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- 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 ns_svg "http://www.w3.org/2000/svg">
+ <!ENTITY ns_xlink " http://www.w3.org/1999/xlink">
+ <!ENTITY stroke_color "#000000">
+ <!ENTITY fill_color "#AAAAAA">
+]>
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.1"
+ width="244"
+ height="244"
+ id="svg3204">
+
+ <defs
+ id="defs3208" />
+ <path
+ style="fill:&fill_color;;fill-opacity:0.75;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:6.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
+ d="m 106.5,240.1082 c -1.1,-0.46153 -5.03277,-1.15999 -8.739478,-1.55214 -3.706714,-0.39215 -6.971572,-1.08853 -7.255241,-1.54751 -0.283669,-0.45899 -2.408599,-1.15432 -4.722066,-1.54518 -2.313468,-0.39085 -4.89861,-1.08923 -5.74476,-1.55193 C 79.192305,233.44873 76.25,232.13583 73.5,230.99388 54.125975,222.94873 34.966239,206.88578 22.813703,188.5 17.193991,179.99784 11.011549,168.1181 11.004882,165.80902 c -0.0027,-0.93004 -0.403869,-1.93757 -0.891519,-2.23896 C 9.6257126,163.26868 8.9199289,161.38672 8.5449548,159.38794 8.1699806,157.38915 7.4911069,155.02168 7.0363466,154.12689 6.5815862,153.2321 5.4477178,148.225 4.516639,143 1.2522102,124.68078 3.8480175,95.074617 9.9965107,80.5 13.077055,73.197763 18.496259,62.360884 19.638599,61.218544 20.38737,60.469773 21,59.420293 21,58.886365 21,58.352437 21.457673,57.37208 22.017051,56.707794 22.576429,56.043507 25.207862,52.575 27.864681,49 33.207795,41.810335 45.232364,30.140896 52.076946,25.502812 54.509626,23.854359 56.725,22.262718 57,21.965832 58.130029,20.745869 78.618889,10.997167 80.05,10.998527 c 0.8525,8.1e-4 1.9775,-0.42272 2.5,-0.941178 2.961999,-2.9390863 34.34366,-7.798945 43.60715,-6.7531349 14.96897,1.6899357 23.44253,2.9713326 24.34285,3.681199 0.55,0.4336515 2.91014,1.1034921 5.24476,1.4885346 2.33462,0.3850425 4.49868,1.1109217 4.80902,1.6130643 0.31034,0.502144 1.23739,0.912988 2.0601,0.912988 1.58844,0 9.02999,3.05859 13.6746,5.620463 1.53366,0.845939 3.78366,2.076434 5,2.734434 4.30099,2.326702 11.22468,6.877496 12.21152,8.026382 0.55,0.64031 3.22326,2.953967 5.94057,5.141461 5.82908,4.692533 11.54778,10.249746 11.58781,11.260593 0.0156,0.394167 1.81864,2.728812 4.00674,5.1881 3.56612,4.008105 13.91358,20.162472 13.99881,21.854822 C 229.05259,71.196815 229.93732,73.3 231,75.5 c 1.06268,2.2 1.94741,4.760942 1.96607,5.690983 0.0187,0.930041 0.45793,1.953029 0.97615,2.273308 0.51822,0.32028 1.24999,2.709337 1.62615,5.309017 0.37615,2.599681 1.02329,5.176692 1.43808,5.726692 1.06989,1.418647 2.99355,19.01601 2.99355,27.3846 0,8.49079 -1.97983,27.22024 -3.02484,28.6154 -0.41197,0.55 -1.06407,2.91014 -1.44911,5.24476 -0.38504,2.33462 -1.11092,4.49868 -1.61306,4.80902 -0.50215,0.31034 -0.91299,1.23739 -0.91299,2.0601 0,1.9624 -6.62941,17.09866 -8.05279,18.38612 -0.30403,0.275 -1.6264,2.3 -2.9386,4.5 -6.76208,11.33713 -16.82979,22.34576 -28.61935,31.29409 -8.13257,6.17267 -26.41048,16.18113 -29.58024,16.19729 -0.93004,0.005 -1.93757,0.40761 -2.23896,0.89526 -0.30138,0.48765 -2.24044,1.20292 -4.30901,1.58948 -2.06858,0.38657 -4.21105,1.05389 -4.76105,1.48293 -3.28084,2.55931 -41.21607,5.15634 -46,3.14915 z M 56.332028,206.83833 C 57.082514,203.96846 55.395741,201 53.014506,201 c -2.409898,0 -3.839009,2.46722 -3.241033,5.59533 0.626837,3.27909 5.715298,3.46761 6.558555,0.243 z m 42.905039,-42.23658 35.465743,-35.39826 5.76786,-1.1974 c 9.19258,-1.90837 18.98968,0.59885 23.25871,5.95226 5.89716,7.39507 6.38173,8.65434 6.38173,16.58426 0,7.07507 -0.27499,8.15769 -3.05555,12.02956 -3.50354,4.87859 -3.69634,6.0623 -1.40057,8.59909 2.41004,2.66307 3.82556,2.2799 7.09767,-1.92126 12.49979,-16.04887 4.79631,-41.35799 -14.78609,-48.57843 -4.89836,-1.80613 -19.43602,-2.33904 -20.41902,-0.74851 -0.31377,0.50769 -2.02782,1.23541 -3.80902,1.61717 -2.52117,0.54035 -11.15747,8.57739 -38.98853,36.28316 C 75.0875,177.39738 59,194.0694 59,194.87234 c 0,1.4274 2.874701,4.95033 4.135663,5.06823 0.349614,0.0327 16.595246,-15.86978 36.101404,-35.33882 z M 68.44113,174.4682 C 69.508751,173.66069 71.038199,173 71.839903,173 74.478904,173 84.142283,164.21559 111.75,136.72008 126.7375,121.7935 139,110.31265 139,111.20709 c 0,0.89443 -0.5625,1.83249 -1.25,2.08458 -0.89031,0.32644 -0.94336,0.78655 -0.18436,1.59897 0.7619,0.81552 9.31284,1.06492 30,0.875 L 196.5,115.5 l 0.33749,-2.38691 c 0.18562,-1.3128 -0.26438,-2.76841 -1,-3.23469 C 195.10187,109.41213 185.275,109.00951 174,108.98371 l -20.5,-0.0469 3,-2.59609 c 1.65,-1.42785 5.25878,-4.67613 8.01951,-7.218397 2.76073,-2.542272 8.93763,-7.955319 13.72645,-12.028992 5.68278,-4.834127 10.00347,-9.523064 12.43978,-13.5 3.67573,-6.000117 3.73345,-6.25393 3.77354,-16.593321 0.0377,-9.732594 -0.15518,-10.801099 -2.63962,-14.619773 C 185.66372,32.91832 175.33298,28.562885 164.70879,30.95029 157.86611,32.487936 149,38.661785 149,41.888986 c 0,0.476369 -0.6145,1.376118 -1.36557,1.999444 -1.75869,1.459587 -2.95845,10.016221 -1.62902,11.618092 0.55401,0.667539 2.07876,1.008882 3.38833,0.758542 1.88333,-0.360022 2.51308,-1.280905 3.01273,-4.405506 0.83569,-5.226163 7.86425,-12.444517 13.06783,-13.420715 6.50766,-1.220843 11.95251,-0.0089 15.8357,3.52469 4.81843,4.384672 6.69,8.742734 6.69,15.578065 0,7.252758 -2.80061,13.095432 -8.99367,18.762751 -10.6572,9.752486 -15.30693,13.845574 -23.65318,20.821554 -4.85242,4.055757 -9.98328,8.620277 -11.40192,10.143377 -1.41864,1.5231 -2.89669,2.45193 -3.28456,2.06405 -0.38788,-0.38787 0.57578,-1.87524 2.14145,-3.30526 1.56568,-1.43002 3.09961,-3.56724 3.40875,-4.74936 C 146.85225,98.848986 144.58679,95 142.52131,95 c -0.7509,0 -17.11172,15.74644 -36.35736,34.99208 -19.245642,19.24564 -36.268249,35.49571 -37.828014,36.11126 -5.120143,2.02064 -15.498729,1.45509 -20.66727,-1.12621 -5.481911,-2.73779 -7.709722,-4.9781 -10.687747,-10.74769 -3.27689,-6.34861 -2.530731,-15.90885 1.773991,-22.72944 2.773104,-4.39383 2.994771,-5.24247 1.828414,-7 -3.408684,-5.13639 -9.36196,0.0157 -12.92759,11.1879 -1.800966,5.64295 -2.208355,13.85253 -0.732671,14.76455 0.507685,0.31377 1.225098,1.94564 1.59425,3.62638 1.520057,6.92077 7.8091,14.64505 14.622251,17.95923 2.39824,1.16659 5.035436,2.50881 5.860436,2.9827 2.614605,1.50187 17.275865,1.08516 19.44113,-0.55256 z M 155.6106,88.584636 C 155.10186,86.557663 154.34388,86 152.09753,86 c -2.24095,0 -2.9657,0.530051 -3.32992,2.435354 -0.57031,2.983364 0.12319,4.19686 2.73239,4.781155 2.92832,0.65576 4.88448,-1.548468 4.1106,-4.631873 z"
+ id="path3216" />
+</svg>
diff --git a/arrow40b-mask.xbm b/arrow40b-mask.xbm
new file mode 100755
index 0000000..be355d1
--- /dev/null
+++ b/arrow40b-mask.xbm
@@ -0,0 +1,20 @@
+#define arrow40b_mask_width 40
+#define arrow40b_mask_height 40
+static unsigned char arrow40b_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xf8, 0xff, 0xff, 0xff, 0x00, 0xf8, 0xff, 0xff, 0xff,
+ 0x03, 0xf8, 0xff, 0xff, 0xff, 0x07, 0xf8, 0xff, 0xff, 0xff, 0x0f, 0xf8,
+ 0xff, 0xff, 0xff, 0x0f, 0xf8, 0xff, 0xff, 0xff, 0x0f, 0xf8, 0xff, 0xff,
+ 0xff, 0x0f, 0xf8, 0xff, 0xff, 0xff, 0x0f, 0xf8, 0xff, 0xff, 0xff, 0x0f,
+ 0xf8, 0xff, 0xff, 0xff, 0x07, 0xf8, 0xff, 0xff, 0xff, 0x03, 0xf8, 0xff,
+ 0xff, 0xff, 0x00, 0xf8, 0xff, 0x7f, 0x00, 0x00, 0xf8, 0xff, 0xff, 0x00,
+ 0x00, 0xf8, 0xff, 0xff, 0x01, 0x00, 0xf8, 0xff, 0xff, 0x03, 0x00, 0xf8,
+ 0xff, 0xff, 0x07, 0x00, 0xf8, 0xff, 0xff, 0x0f, 0x00, 0xf8, 0xff, 0xff,
+ 0x1f, 0x00, 0xf8, 0xff, 0xff, 0x3f, 0x00, 0xf8, 0xff, 0xff, 0x7f, 0x00,
+ 0xf8, 0x3f, 0xff, 0xff, 0x00, 0xf8, 0x3f, 0xfe, 0xff, 0x01, 0xf8, 0x3f,
+ 0xfc, 0xff, 0x03, 0xf8, 0x3f, 0xf8, 0xff, 0x07, 0xf8, 0x3f, 0xf0, 0xff,
+ 0x0f, 0xf8, 0x3f, 0xe0, 0xff, 0x1f, 0xf8, 0x3f, 0xc0, 0xff, 0x1f, 0xf8,
+ 0x3f, 0x80, 0xff, 0x1f, 0xf8, 0x3f, 0x00, 0xff, 0x1f, 0xf8, 0x3f, 0x00,
+ 0xfe, 0x1f, 0xf0, 0x1f, 0x00, 0xfc, 0x1f, 0xe0, 0x0f, 0x00, 0xf8, 0x0f,
+ 0x00, 0x01, 0x00, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
diff --git a/arrow40b.xbm b/arrow40b.xbm
new file mode 100755
index 0000000..d04b182
--- /dev/null
+++ b/arrow40b.xbm
@@ -0,0 +1,20 @@
+#define arrow40b_width 40
+#define arrow40b_height 40
+static unsigned char arrow40b_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xe0, 0xff, 0xff, 0x7f, 0x00, 0xe0, 0xff, 0xff, 0xff, 0x01, 0xe0,
+ 0xff, 0xff, 0xff, 0x03, 0xe0, 0xff, 0xff, 0xff, 0x03, 0xe0, 0xff, 0xff,
+ 0xff, 0x03, 0xe0, 0xff, 0xff, 0xff, 0x01, 0xe0, 0xff, 0xff, 0xff, 0x01,
+ 0xe0, 0xff, 0xff, 0x7f, 0x00, 0xe0, 0xff, 0x03, 0x00, 0x00, 0xe0, 0xff,
+ 0x07, 0x00, 0x00, 0xe0, 0xff, 0x0f, 0x00, 0x00, 0xe0, 0xff, 0x1f, 0x00,
+ 0x00, 0xe0, 0xff, 0x3f, 0x00, 0x00, 0xe0, 0xff, 0x7f, 0x00, 0x00, 0xe0,
+ 0xcf, 0xff, 0x00, 0x00, 0xe0, 0x8f, 0xff, 0x01, 0x00, 0xe0, 0x0f, 0xff,
+ 0x03, 0x00, 0xe0, 0x0f, 0xfe, 0x07, 0x00, 0xe0, 0x0f, 0xfc, 0x0f, 0x00,
+ 0xe0, 0x0f, 0xf8, 0x1f, 0x00, 0xe0, 0x0f, 0xf0, 0x3f, 0x00, 0xe0, 0x0f,
+ 0xe0, 0x7f, 0x00, 0xe0, 0x0f, 0xc0, 0xff, 0x00, 0xe0, 0x0f, 0x80, 0xff,
+ 0x01, 0xe0, 0x0f, 0x00, 0xff, 0x03, 0xe0, 0x0f, 0x00, 0xfe, 0x07, 0xe0,
+ 0x0f, 0x00, 0xfc, 0x07, 0xe0, 0x0f, 0x00, 0xf8, 0x07, 0xc0, 0x07, 0x00,
+ 0xf0, 0x07, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x40, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
diff --git a/olpcgames/COPYING b/olpcgames/COPYING
new file mode 100755
index 0000000..b8adee0
--- /dev/null
+++ b/olpcgames/COPYING
@@ -0,0 +1,24 @@
+* Copyright (c) 2007, One Laptop Per Child.
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+* * Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+* * Redistributions in binary form must reproduce the above copyright
+* notice, this list of conditions and the following disclaimer in the
+* documentation and/or other materials provided with the distribution.
+* * Neither the name of One Laptop Per Child nor the
+* names of its contributors may be used to endorse or promote products
+* derived from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY ONE LAPTOP PER CHILD ``AS IS'' AND ANY
+* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL ONE LAPTOP PER CHILD BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/olpcgames/__init__.py b/olpcgames/__init__.py
new file mode 100755
index 0000000..504388c
--- /dev/null
+++ b/olpcgames/__init__.py
@@ -0,0 +1,102 @@
+"""Wrapper/adaptation system for writing/porting Pygame games to OLPC/Sugar
+
+The wrapper system attempts to substitute various pieces of the Pygame
+implementation in order to make code written without knowledge of the
+OLPC/Sugar environment run "naturally" under the GTK environment of
+Sugar. It also provides some convenience mechanisms for dealing with
+e.g. the Camera and Mesh Network system.
+
+Considerations for Developers:
+
+Pygame programs running under OLPCGames will generally not have
+"hardware" surfaces, and will not be able to have a reduced-resolution
+full-screen view to optimise rendering. The Pygame code will run in
+a secondary thread, with the main GTK UI running in the primary thread.
+A third "mainloop" thread will occasionally be created to handle the
+GStreamer interface to the camera.
+
+Attributes of Note:
+
+ ACTIVITY -- if not None, then the activity instance which represents
+ this activity at the Sugar shell level.
+ WIDGET -- PygameCanvas instance, a GTK widget with an embedded
+ socket object which is a proxy for the SDL window Pygame to which
+ pygame renders.
+
+ Constants: All event constants used by the package are defined at this
+ level. Note that eventually we will need to switch to using UserEvent
+ and making these values sub-types rather than top-level types.
+
+
+Pygame events at the Activity Level:
+
+ pygame.USEREVENT
+ code == olpcgames.FILE_READ_REQUEST
+ filename (unicode/string) -- filename from which to read
+ metadata (dictionary-like) -- mapping from key to string values
+
+ Note: due to a limitation in the Sugar API, the GTK event loop
+ will be *frozen* during this operation, as a result you cannot
+ make any DBUS or GTK calls, nor can you use GUI during the
+ call to provide input. That is, you have to process this event
+ synchronously.
+
+ code == olpcgames.FILE_WRITE_REQUEST
+ filename (unicode/string) -- file name to which to write
+ metadata (dictionary-like) -- mapping from key: value where all
+ values must (currently) be strings
+
+ Note: due to a limitation in the Sugar API, the GTK event loop
+ will be *frozen* during this operation, as a result you cannot
+ make any DBUS or GTK calls, nor can you use GUI during the
+ call to provide input. That is, you have to process this event
+ synchronously.
+
+see also the mesh and camera modules for more events.
+
+Deprecated:
+
+ This module includes the activity.PyGameActivity class currently,
+ this is a deprecated mechanism for accessing the activity class,
+ and uses the deprecated spelling (case) of the name. Use:
+
+ from olpcgames import activity
+
+ class MyActivity( activity.PygameActivity ):
+ ...
+
+ to define your PygameActivity subclass (note the case of the
+ spelling, which now matches Pygame's own spelling).
+"""
+from olpcgames._version import __version__
+ACTIVITY = None
+widget = WIDGET = None
+
+# XXX problem here, we're filling up the entirety of the Pygame
+# event-set with just this small bit of functionality, obviously
+# Pygame is not intending for this kind of usage!
+(
+ CAMERA_LOAD, CAMERA_LOAD_FAIL,
+
+ CONNECT,PARTICIPANT_ADD,
+ PARTICIPANT_REMOVE,
+ MESSAGE_UNI,MESSAGE_MULTI,
+) = range( 25, 32 )
+
+# These events use UserEvent.code, eventually *all* events should be
+# delivered as UserEvent with code set to the values defined here...
+
+(
+ #NET_CONNECT, NET_PARTICIPANT_ADD,NET_PARTICIPANT_REMOVE,
+ #NET_MESSAGE_UNICAST, NET_MESSAGE_MULTICAST,
+ #CAMERA_LOAD, CAMERA_LOAD_FAIL,
+ FILE_READ_REQUEST, FILE_WRITE_REQUEST,
+) = range(
+ 2**16, 2**16+2,
+)
+
+try:
+ from olpcgames.activity import PygameActivity as PyGameActivity
+except ImportError, err:
+ PyGameActivity = None
+
diff --git a/olpcgames/_cairoimage.py b/olpcgames/_cairoimage.py
new file mode 100755
index 0000000..3cfa22c
--- /dev/null
+++ b/olpcgames/_cairoimage.py
@@ -0,0 +1,135 @@
+"""Utility functions for cairo-specific operations
+
+USE_BASE_ARRAY -- if False (default), uses numpy arrays,
+ currently this is the only version that works on 32-bit
+ machines.
+"""
+import pygame, struct, logging
+big_endian = struct.pack( '=i', 1 ) == struct.pack( '>i', 1 )
+
+log = logging.getLogger( 'olpcgames._cairoimage' )
+##log.setLevel( logging.DEBUG )
+
+USE_BASE_ARRAY = False
+
+def newContext( width, height ):
+ """Create a new render-to-image context
+
+ width, height -- pixel dimensions to be rendered
+
+ Produces an ARGB format Cairo ImageSurface for
+ rendering your data into using rsvg, Cairo or Pango.
+
+ returns (ImageSurface, CairoContext) for rendering
+ """
+ import cairo
+ csrf = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
+ context = cairo.Context (csrf)
+ #log.info( 'Format (expect: %s): %s', cairo.FORMAT_ARGB32, csrf.get_format())
+ return csrf, context
+
+def mangle_color(color):
+ """Mange a colour depending on endian-ness, and swap-necessity
+
+ Converts a 3 or 4 int (or float) value in the range 0-255 into a
+ 4-float value in the range 0.0-1.0
+ """
+ r,g,b = color[:3]
+ if len(color) > 3:
+ a = color[3]
+ else:
+ a = 255.0
+ return map(_fixColorBase, (r,g,b,a) )
+
+def _fixColorBase( v ):
+ """Return a properly clamped colour in floating-point space"""
+ return max((0,min((v,255.0))))/255.0
+
+def asImage( csrf ):
+ """Get the pixels in csrf as a Pygame image
+
+ Note that Pygame 1.7.1 on (Gentoo Linux) AMD64 is incorrectly
+ calculating the required size ARGB images, so this code will *not* work
+ on that platform with that version of the library. Pygame-ctypes
+ does work correctly there.
+
+ Note also that Pygame 1.7.1 is showing a strange colour rotation
+ bug on 32-bit platforms, such that ARGB mode cannot be used for
+ images there. Instead we have to do an expensive bit-shift operation
+ to produce an RGBA image from the ARGB native Cairo format.
+
+ Will raise a ValueError if passed a Null image (i.e. dimension of 0)
+
+ returns Pygame.Surface (image) with convert_alpha() called for it.
+ """
+ # Create and return a new Pygame Image derived from the Cairo Surface
+ format = 'ARGB'
+ if hasattr(csrf,'get_data'):
+ # more recent API, native-format, but have to (potentially) convert the format...
+ log.debug( 'Native-mode api (get_data)' )
+ data = csrf.get_data()
+ if not big_endian:
+ # we use array here because it's considerably lighter-weight
+ # to import than the numpy module
+ log.debug( 'Not big-endian, byte-swapping array' )
+ if USE_BASE_ARRAY:
+ import array
+ a = array.array( 'I' )
+ a.fromstring( data )
+ a.byteswap()
+ data = a.tostring()
+ else:
+ import numpy
+ n = numpy.fromstring( data, dtype='I' )
+ n = ((n & 0xff000000) >> 24 ) | ((n & 0x00ffffff) << 8 )
+ n = n.byteswap()
+ data = n.tostring()
+ format = 'RGBA'
+ else:
+ log.debug( 'Big-endian, array unchanged' )
+ data = str(data) # there's one copy
+ else:
+ # older api, not native, but we know what it is...
+ log.debug( 'Non-native mode api, explicitly RGBA' )
+ data = csrf.get_data_as_rgba()
+ data = str(data) # there's one copy
+ format = 'RGBA'
+ width, height = csrf.get_width(),csrf.get_height()
+
+ try:
+ log.info( 'Format = %s', format )
+ return pygame.image.fromstring(
+ data,
+ (width,height),
+ format
+ ) # there's the next
+ except ValueError, err:
+ err.args += (len(data), (width,height), width*height*4,format )
+ raise
+
+if __name__ == "__main__":
+ import unittest
+ logging.basicConfig()
+ class Tests( unittest.TestCase ):
+ def test_colours( self ):
+ """Test that colours are correctly translated
+
+ If we draw a given colour in cairo, we want the same
+ colour to show up in Pygame, let's test that...
+ """
+ for sourceColour in [
+ (255,0,0, 255),
+ (0,255,0, 255),
+ (0,0,255, 255),
+ (255,255,0, 255),
+ (0,255,255,255),
+ (255,0,255,255),
+ ]:
+ csrf,cctx = newContext( 1,1 )
+ background = mangle_color( sourceColour )
+ cctx.set_source_rgba(*background)
+ cctx.paint()
+ img = asImage( csrf )
+ colour = img.get_at( (0,0))
+ assert colour == sourceColour, (sourceColour,mangle_color(sourceColour),colour)
+ unittest.main()
diff --git a/olpcgames/_gtkmain.py b/olpcgames/_gtkmain.py
new file mode 100755
index 0000000..33a6a83
--- /dev/null
+++ b/olpcgames/_gtkmain.py
@@ -0,0 +1,70 @@
+"""Support for GObject mainloop-requiring libraries when not inside GTK
+
+INITIALIZED -- whether we have a running gobject loop yet...
+LOOP_TRACKER -- if present, the manual gtk event loop used to
+ support gobject-based code running in a non-Gobject event loop
+
+Holder -- objects which can be held as attributes to keep the mainloop running
+"""
+import threading, logging
+log = logging.getLogger( 'olpcgames._gtkmain' )
+##log.setLevel( logging.DEBUG )
+
+INITIALIZED = False
+LOOP_TRACKER = None
+
+class _TrackLoop( object ):
+ """Tracks the number of open loops and stops when finished"""
+ count = 0
+ _mainloop = None
+ def increment( self ):
+ log.info( 'Increment from %s', self.count )
+ self.count += 1 # XXX race condition here?
+ if self.count == 1:
+ log.info( 'Creating GObject mainloop')
+ self.t_loop = threading.Thread(target=self.loop)
+ self.t_loop.setDaemon( True )
+ self.t_loop.start()
+ def decrement( self ):
+ log.info( 'Decrement from %s', self.count )
+ self.count -= 1
+ def loop( self ):
+ """Little thread loop that replicates the gtk mainloop"""
+ import gtk
+ while self.count >= 1:
+ log.debug( 'GTK loop restarting' )
+ while gtk.events_pending():
+ gtk.main_iteration()
+ log.debug( 'GTK loop exiting' )
+ try:
+ del self.t_loop
+ except AttributeError, err:
+ pass
+
+class Holder():
+ """Object which, while held, keeps the gtk mainloop running"""
+ def __init__( self ):
+ log.info( 'Beginning hold on GTK mainloop with Holder object' )
+ startGTK()
+ def __del__( self ):
+ log.info( 'Releasing hold on GTK mainloop with Holder object' )
+ stopGTK()
+
+def startGTK( ):
+ """GTK support is required here, process..."""
+ if not INITIALIZED:
+ init()
+ if LOOP_TRACKER:
+ LOOP_TRACKER.increment()
+def stopGTK( ):
+ """GTK support is no longer required, release"""
+ if LOOP_TRACKER:
+ LOOP_TRACKER.decrement()
+def init( ):
+ """Create a gobject mainloop in a sub-thread (you don't need to call this normally)"""
+ global INITIALIZED, LOOP_TRACKER
+ if not INITIALIZED:
+ if not LOOP_TRACKER:
+ LOOP_TRACKER = _TrackLoop()
+ INITIALIZED = True
+ return LOOP_TRACKER
diff --git a/olpcgames/_version.py b/olpcgames/_version.py
new file mode 100755
index 0000000..6a4e1db
--- /dev/null
+++ b/olpcgames/_version.py
@@ -0,0 +1,2 @@
+"""Module defining the current version of the library"""
+__version__ = '1.6'
diff --git a/olpcgames/activity.py b/olpcgames/activity.py
new file mode 100755
index 0000000..538ba13
--- /dev/null
+++ b/olpcgames/activity.py
@@ -0,0 +1,241 @@
+"""Embeds the Canvas widget into a Sugar-specific Activity environment
+
+The olpcgames.activity module encapsulates creation of a Pygame activity.
+Your Activity should inherit from this class. Simply setting some class
+attributes is all you need to do in a class inheriting from
+olpcgames.activity.PygameActivity in order to get Pygame to work.
+
+(The skeleton builder script creates this file automatically for you).
+
+Note:
+ You should not import pygame into your activity file, as the olpcgames
+ wrapper needs to be initialized before pygame is imported the first time.
+
+Example usage:
+
+ class PygameActivity(activity.Activity):
+ game_name = None
+ game_title = 'Pygame Game'
+ game_size = (units.grid_to_pixels(16),
+ units.grid_to_pixels(11))
+ pygame_mode = 'SDL'
+"""
+import logging
+logging.root.setLevel( logging.WARN )
+log = logging.getLogger( 'olpcgames.activity' )
+##log.setLevel( logging.DEBUG )
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gtk.gdk
+import os
+
+from sugar.activity import activity
+from sugar.graphics import style
+from olpcgames.canvas import PygameCanvas
+from olpcgames import mesh, util
+
+__all__ = ['PygameActivity']
+
+class PygameActivity(activity.Activity):
+ """Pygame-specific activity type, provides boilerplate toolbar, creates canvas
+
+ Subclass Overrides:
+
+ game_name -- specifies a fully-qualified name for the game's main-loop
+ format like so:
+ 'package.module:main'
+ if no function name is provided, "main" is assumed.
+
+ game_handler -- DEPRECATED. alternate specification via direct
+ reference to a main-loop function.
+
+ game_size -- two-value tuple specifying the size of the display in pixels,
+ this is currently static, so once the window is created it cannot be
+ changed.
+
+ If None, use the bulk of the screen for the Pygame surface based on
+ the values reported by the gtk.gdk functions. Note that None is
+ *not* the default value.
+
+ game_title -- title to be displayed in the Sugar Shell UI
+
+ pygame_mode -- chooses the rendering engine used for handling the
+ Pygame drawing mode, 'SDL' chooses the standard Pygame renderer,
+ 'Cairo' chooses the experimental pygamecairo renderer.
+
+ Note: You likely do *not* want to use Cairo, it is no longer maintained.
+
+ PYGAME_CANVAS_CLASS -- normally PygameCanvas, but can be overridden
+ if you want to provide a different canvas class, e.g. to provide a different
+ internal layout. Note: only used where pygame_mode == 'SDL'
+
+ The Activity, once created, will be made available as olpcgames.ACTIVITY,
+ and that access mechanism should allow code to test for the presence of the
+ activity before accessing Sugar-specific functionality.
+
+ XXX Note that currently the toolbar and window layout are hard-coded into
+ this super-class, with no easy way of overriding without completely rewriting
+ the __init__ method. We should allow for customising both the UI layout and
+ the toolbar contents/layout/connection.
+
+ XXX Note that if you change the title of your activity in the toolbar you may
+ see the same focus issues as we have patched around in the build_toolbar
+ method. If so, please report them to Mike Fletcher.
+ """
+ game_name = None
+ game_title = 'Pygame Game'
+ game_handler = None
+ game_size = (16 * style.GRID_CELL_SIZE,
+ 11 * style.GRID_CELL_SIZE)
+ pygame_mode = 'SDL'
+
+ def __init__(self, handle):
+ """Initialise the Activity with the activity-description handle"""
+ super(PygameActivity, self).__init__(handle)
+ self.make_global()
+ if self.game_size is None:
+ width,height = gtk.gdk.screen_width(), gtk.gdk.screen_height()
+ log.info( 'Total screen size: %s %s', width,height)
+ # for now just fudge the toolbar size...
+ self.game_size = width, height - (1*style.GRID_CELL_SIZE)
+ self.set_title(self.game_title)
+ toolbar = self.build_toolbar()
+ log.debug( 'Toolbar size: %s', toolbar.get_size_request())
+ canvas = self.build_canvas()
+ self.connect( 'configure-event', canvas._translator.do_resize_event )
+
+ def make_global( self ):
+ """Hack to make olpcgames.ACTIVITY point to us
+ """
+ import weakref, olpcgames
+ assert not olpcgames.ACTIVITY, """Activity.make_global called twice, have you created two Activity instances in a single process?"""
+ olpcgames.ACTIVITY = weakref.proxy( self )
+
+ def build_toolbar( self ):
+ """Build our Activity toolbar for the Sugar system
+
+ This is a customisation point for those games which want to
+ provide custom toolbars when running under Sugar.
+ """
+ toolbar = activity.ActivityToolbar(self)
+ toolbar.show()
+ self.set_toolbox(toolbar)
+ def shared_cb(*args, **kwargs):
+ log.info( 'shared: %s, %s', args, kwargs )
+ try:
+ mesh.activity_shared(self)
+ except Exception, err:
+ log.error( """Failure signaling activity sharing to mesh module: %s""", util.get_traceback(err) )
+ else:
+ log.info( 'mesh activity shared message sent, trying to grab focus' )
+ try:
+ self._pgc.grab_focus()
+ except Exception, err:
+ log.warn( 'Focus failed: %s', err )
+ else:
+ log.info( 'asserting focus' )
+ assert self._pgc.is_focus(), """Did not successfully set pygame canvas focus"""
+ log.info( 'callback finished' )
+
+ def joined_cb(*args, **kwargs):
+ log.info( 'joined: %s, %s', args, kwargs )
+ mesh.activity_joined(self)
+ self._pgc.grab_focus()
+ self.connect("shared", shared_cb)
+ self.connect("joined", joined_cb)
+
+ if self.get_shared():
+ # if set at this point, it means we've already joined (i.e.,
+ # launched from Neighborhood)
+ joined_cb()
+
+ toolbar.title.unset_flags(gtk.CAN_FOCUS)
+ return toolbar
+
+ PYGAME_CANVAS_CLASS = PygameCanvas
+ def build_canvas( self ):
+ """Construct the Pygame or PygameCairo canvas for drawing"""
+ assert self.game_handler or self.game_name, 'You must specify a game_handler or game_name on your Activity (%r)'%(
+ self.game_handler or self.game_name
+ )
+ if self.pygame_mode != 'Cairo':
+ self._pgc = self.PYGAME_CANVAS_CLASS(*self.game_size)
+ self.set_canvas(self._pgc)
+ self._pgc.grab_focus()
+ self._pgc.connect_game(self.game_handler or self.game_name)
+ # XXX Bad coder, do not hide in a widely subclassed operation
+ # map signal does not appear to show up on socket instances
+ gtk.gdk.threads_init()
+ return self._pgc
+ else:
+ import hippo
+ self._drawarea = gtk.DrawingArea()
+ canvas = hippo.Canvas()
+ canvas.grab_focus()
+ self.set_canvas(canvas)
+ self.show_all()
+
+ import pygamecairo
+ pygamecairo.install()
+
+ pygamecairo.display.init(canvas)
+ app = self.game_handler or self.game_name
+ if ':' not in app:
+ app += ':main'
+ mod_name, fn_name = app.split(':')
+ mod = __import__(mod_name, globals(), locals(), [])
+ fn = getattr(mod, fn_name)
+ fn()
+ def read_file(self, file_path):
+ """Handle request to read the given file on the Pygame side
+
+ This is complicated rather noticeably by the silly semantics of the Journal
+ where it unlinks the file as soon as this method returns. We either have to
+ handle the file-opening in PyGTK (not acceptable), block this thread until
+ the Pygame thread handles the event (which it may never do) or we have
+ to make the silly thing use a non-standard file-opening interface.
+ """
+ log.info( 'read_file: %s %s', file_path, self.metadata )
+ import olpcgames, pygame
+ from olpcgames import eventwrap
+ event = eventwrap.Event(
+ type = pygame.USEREVENT,
+ code = olpcgames.FILE_READ_REQUEST,
+ filename = file_path,
+ metadata = self.metadata,
+ )
+ eventwrap.post( event )
+ event.block()
+ def write_file( self, file_path ):
+ """Handle request to write to the given file on the Pygame side
+
+ This is rather complicated by the need to have the file complete by the
+ time the function returns. Very poor API, after all, if I have to write a
+ multi-hundred-megabyte file it might take many minutes to complete
+ writing.
+ """
+ log.info( 'write_file: %s %s', file_path, self.metadata )
+ if os.path.exists( file_path ):
+ self.read_file( file_path )
+ import olpcgames, pygame
+ from olpcgames import eventwrap
+ event = eventwrap.Event(
+ type = pygame.USEREVENT,
+ code = olpcgames.FILE_WRITE_REQUEST,
+ filename = file_path,
+ metadata = self.metadata,
+ )
+ eventwrap.post( event )
+ event.block()
+ if not os.path.exists( file_path ):
+ log.warn( '''No file created in %r''', file_path )
+ raise NotImplementedError( """Pygame Activity code did not produce a file for %s"""%( file_path, ))
+ else:
+ log.info( '''Stored file in %r''', file_path )
+
+
+import olpcgames
+olpcgames.PyGameActivity = PygameActivity
+PyGameActivity = PygameActivity
diff --git a/olpcgames/buildmanifest.py b/olpcgames/buildmanifest.py
new file mode 100755
index 0000000..899433b
--- /dev/null
+++ b/olpcgames/buildmanifest.py
@@ -0,0 +1,33 @@
+#! /usr/bin/env python
+"""Stupid little script to automate generation of MANIFEST and po/POTFILES.in
+
+Really this should have been handled by using distutils, but oh well,
+distutils is a hoary beast and I can't fault people for not wanting to
+spend days spelunking around inside it to find the solutions...
+"""
+from distutils.filelist import FileList
+import os
+
+def fileList( template ):
+ """Produce a formatted file-list for storing in a file"""
+ files = FileList()
+ for line in filter(None,template.splitlines()):
+ files.process_template_line( line )
+ content = '\n'.join( files.files )
+ return content
+
+
+def main( ):
+ """Do the quicky finding of files for our manifests"""
+ content = fileList( open('MANIFEST.in').read() )
+ open( 'MANIFEST','w').write( content )
+
+ content = fileList( open('POTFILES.in').read() )
+ try:
+ os.makedirs( 'po' )
+ except OSError, err:
+ pass
+ open( os.path.join('po','POTFILES.in'), 'w').write( content )
+
+if __name__ == "__main__":
+ main()
diff --git a/olpcgames/camera.py b/olpcgames/camera.py
new file mode 100755
index 0000000..249f295
--- /dev/null
+++ b/olpcgames/camera.py
@@ -0,0 +1,221 @@
+"""Accesses OLPC Camera functionality via gstreamer
+
+Depends upon:
+ pygame
+ gstreamer (particularly gst-launch)
+
+Activity demonstrating usage:
+
+ http://dev.laptop.org/git?p=projects/games-misc;a=tree;f=cameratest.activity;hb=HEAD
+
+
+"""
+import threading, subprocess
+import logging
+import olpcgames
+import time
+import os
+import pygame
+from olpcgames.util import get_activity_root
+
+log = logging.getLogger( 'olpcgames.camera' )
+#log.setLevel( logging.DEBUG )
+
+CAMERA_LOAD, CAMERA_LOAD_FAIL = olpcgames.CAMERA_LOAD, olpcgames.CAMERA_LOAD
+
+class Camera(object):
+ """A class representing a still-picture camera
+
+ Produces a simple gstreamer bus that terminates in a filesink, that is,
+ it stores the results in a file. When a picture is "snapped" the gstreamer
+ stream is iterated until it finishes processing and then the file can be
+ read.
+
+ There are two APIs available, a synchronous API which can potentially
+ stall your activity's GUI (and is NOT recommended) and an
+ asynchronous API which returns immediately and delivers the captured
+ camera image via a Pygame event. To be clear, it is recommended
+ that you use the snap_async method, *not* the snap method.
+
+ Note:
+
+ The Camera class is simply a convenience wrapper around a fairly
+ straightforward gst-launch bus. If you have more involved
+ requirements for your camera manipulations you will probably
+ find it easier to write your own camera implementation than to
+ use this one. Basically we provide here the "normal" use case of
+ snapping a picture into a pygame image.
+
+ Note:
+
+ With the current camera implementation taking a single photograph
+ requires about 6 seconds! Obviously we'll need to figure out what's
+ taking gstreamer so long to process the pipe and fix that.
+
+ """
+ _aliases = {
+ 'camera': 'v4l2src',
+ 'test': 'videotestsrc',
+ 'testing': 'videotestsrc',
+ 'png': 'pngenc',
+ 'jpeg': 'jpegenc',
+ 'jpg': 'jpegenc',
+ }
+ def __init__(self, source='camera', format='png', filename=None, directory = None):
+ """Initialises the Camera's internal description
+
+ source -- the gstreamer source for the video to capture, useful values:
+ 'v4l2src','camera' -- the camera
+ 'videotestsrc','test' -- test pattern generator source
+ format -- the gstreamer encoder to use for the capture, useful values:
+ 'pngenc','png' -- PNG format graphic
+ 'jpegenc','jpg','jpeg' -- JPEG format graphic
+ filename -- the filename to use for the capture, if not specified defaults
+ to a random UUID + '.' + format
+ directory -- the directory in which to create the temporary file, defaults
+ to get_activity_root() + 'tmp'
+ """
+ log.info( 'Creating camera' )
+ if not filename:
+ import uuid
+ filename = '%s.%s'%( uuid.uuid4(), format )
+ self.source = self._aliases.get( source, source )
+ self.format = self._aliases.get( format, format )
+ self.filename = filename
+ self.directory = directory
+ SNAP_PIPELINE = 'gst-launch','%(source)s','!','ffmpegcolorspace','!','%(format)s','!','filesink','location="%(filename)s"'
+ def _create_subprocess( self ):
+ """Method to create the gstreamer subprocess from our settings"""
+ if not self.directory:
+ path = os.path.join( get_activity_root(), 'tmp' )
+ try:
+ os.makedirs( path )
+ log.info( 'Created temporary directory: %s', path )
+ except (OSError,IOError), err:
+ pass
+ else:
+ path = self.directory
+ filename = os.path.join( path, self.filename )
+ format = self.format
+ source = self.source
+ pipeline = [s%locals() for s in self.SNAP_PIPELINE ]
+ return filename, subprocess.Popen(
+ pipeline,stderr = subprocess.PIPE
+ )
+
+ def snap(self):
+ """Snap a picture via the camera by iterating gstreamer until finished
+
+ Note: this is an unsafe implementation, it will cause the whole
+ activity to hang until the capture finishes. Time to finish is often
+ measured in whole seconds (3-6s).
+
+ It is *strongly* recommended that you use snap_async instead of snap!
+ """
+ log.debug( 'Starting snap' )
+ filename, pipe = self._create_subprocess()
+ if not pipe.wait():
+ log.debug( 'Ending snap, loading: %s', filename )
+ return self._load_and_clean( filename )
+ else:
+ raise IOError( """Unable to complete snapshot: %s""", pipe.stderr.read() )
+ def _load_and_clean( self, filename ):
+ """Use pygame to load given filename, delete after loading/attempt"""
+ try:
+ log.info( 'Loading snapshot file: %s', filename )
+ return pygame.image.load(filename)
+ finally:
+ try:
+ os.remove( filename )
+ except (IOError,OSError), err:
+ pass
+ def snap_async( self, token=None ):
+ """Snap a picture asynchronously generating event on success/failure
+
+ token -- passed back as attribute of the event which signals that capture
+ is finished
+
+ We return events of type CAMERA_LOAD with an attribute "succeed"
+ depending on whether we succeed or not. Attributes of the events which
+ are returned:
+
+ success -- whether the loading process succeeded
+ token -- as passed to this method
+ image -- pygame image.load result if successful, None otherwise
+ filename -- the filename in our temporary directory we used to store
+ the file temporarily (this file will be deleted before the event
+ is sent, the name is for informational purposes only).
+ err -- Exception instance if failed, None otherwise
+
+ Basically identical to the snap method, save that it posts a message
+ to the event bus in pygame.event instead of blocking and returning...
+
+ Example:
+ if event == pygame.MOUSEBUTTONDOWN:
+ camera = Camera( source='test', filename = 'picture32' )
+ camera.snap_async( myIdentifier )
+ ...
+ elif event.type == olpcgames.CAMERA_LOAD:
+ if event.token == myIdentifier:
+ doSomething( event.image )
+ """
+ log.debug( 'beginning async snap')
+ t = threading.Thread(target=self._background_snap, args=[token])
+ t.start()
+ return token
+
+ def _background_snap(
+ self,
+ token = None,
+ ):
+ """Process gst messages until pipe is finished
+
+ pipe -- gstreamer pipe definition for parse_launch, normally it will
+ produce a file into which the camera should store an image
+
+ We consider pipe to be finished when we have had two "state changed"
+ gstreamer events where the pending state is VOID, the first for when
+ we begin playing, the second for when we finish.
+ """
+ log.debug( 'Background thread kicking off gstreamer capture begun' )
+ from pygame.event import Event, post
+ filename, pipe = self._create_subprocess()
+ if not pipe.wait():
+ success = True
+ log.debug( 'Ending capture, loading: %s', filename )
+ try:
+ image = self._load_and_clean( filename )
+ except Exception, err:
+ image = None
+ success = False
+ else:
+ err = None
+ else:
+ success = False
+ err = pipe.stderr.read()
+ image = None
+ evt = Event(
+ CAMERA_LOAD,
+ dict(
+ filename=filename,
+ success = success,
+ token = token,
+ image=image,
+ err=err
+ )
+ )
+ post( evt )
+
+def snap():
+ """Dump a snapshot from the camera to a pygame surface in background thread
+
+ See Camera.snap
+ """
+ return Camera().snap()
+
+def snap_async( token=None, **named ):
+ """Dump snapshot from camera return asynchronously as event in Pygame
+
+ See Camera.snap_async
+ """
+ return Camera(**named).snap_async( token )
diff --git a/olpcgames/canvas.py b/olpcgames/canvas.py
new file mode 100755
index 0000000..2583827
--- /dev/null
+++ b/olpcgames/canvas.py
@@ -0,0 +1,171 @@
+"""Implements bridge connection between Sugar/GTK and Pygame"""
+import os
+import sys
+import logging
+log = logging.getLogger( 'olpcgames.canvas' )
+##log.setLevel( logging.DEBUG )
+import threading
+from pprint import pprint
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+import pygame
+
+from olpcgames import gtkEvent, util
+
+__all__ = ['PygameCanvas']
+
+class PygameCanvas(gtk.Layout):
+ """Canvas providing bridge methods to run Pygame in GTK
+
+ The PygameCanvas creates a secondary thread in which the Pygame instance will
+ live, providing synthetic Pygame events to that thread via a Queue. The GUI
+ connection is done by having the Pygame canvas use a GTK Port object as it's
+ window pointer, it draws to that X-level window in order to produce output.
+ """
+ mod_name = None
+ def __init__(self, width, height):
+ """Initializes the Canvas Object
+
+ width,height -- passed to the inner EventBox in order to request a given size,
+ the Socket is the only child of this EventBox, and the Pygame commands
+ will be writing to the Window ID of the socket. The internal EventBox is
+ centered via an Alignment instance within the PygameCanvas instance.
+
+ XXX Should refactor so that the internal setup can be controlled by the
+ sub-class, e.g. to get size from the host window, or something similar.
+ """
+ # Build the main widget
+ log.info( 'Creating the pygame canvas' )
+ super(PygameCanvas,self).__init__()
+ self.set_flags(gtk.CAN_FOCUS)
+
+ # Build the sub-widgets
+ self._align = gtk.Alignment(0.5, 0.5)
+ self._inner_evb = gtk.EventBox()
+ self._socket = gtk.Socket()
+
+
+ # Add internal widgets
+ self._inner_evb.set_size_request(width, height)
+ self._inner_evb.add(self._socket)
+
+ self._socket.show()
+
+ self._align.add(self._inner_evb)
+ self._inner_evb.show()
+
+ self._align.show()
+
+ self.put(self._align, 0,0)
+
+ # Construct a gtkEvent.Translator
+ self._translator = gtkEvent.Translator(self, self._inner_evb)
+ # <Cue Thus Spract Zarathustra>
+ self.show()
+ def connect_game(self, app):
+ """Imports the given main-loop and starts processing in secondary thread
+
+ app -- fully-qualified Python path-name for the game's main-loop, with
+ name within module as :functionname, if no : character is present then
+ :main will be assumed.
+
+ Side effects:
+
+ Sets the SDL_WINDOWID variable to our socket's window ID
+ Calls Pygame init
+ Causes the gtkEvent.Translator to "hook" Pygame
+ Creates and starts secondary thread for Game/Pygame event processing.
+ """
+ log.info( 'Connecting the pygame canvas' )
+ # Setup the embedding
+ os.environ['SDL_WINDOWID'] = str(self._socket.get_id())
+ #print 'Socket ID=%s'%os.environ['SDL_WINDOWID']
+ pygame.init()
+
+ self._translator.hook_pygame()
+
+ # Load the modules
+ # NOTE: This is delayed because pygame.init() must come after the embedding is up
+ if ':' not in app:
+ app += ':main'
+ mod_name, fn_name = app.split(':')
+ self.mod_name = mod_name
+ mod = __import__(mod_name, globals(), locals(), [])
+ fn = getattr(mod, fn_name)
+
+ # Start Pygame
+ self.__thread = threading.Thread(target=self._start, args=[fn])
+ self.__thread.start()
+
+ def _start(self, fn):
+ """The method that actually runs in the background thread"""
+ log.info( 'Staring the mainloop' )
+ import olpcgames
+ olpcgames.widget = olpcgames.WIDGET = self
+ try:
+ import sugar.activity.activity,os
+ except ImportError, err:
+ log.info( """Running outside Sugar""" )
+ else:
+ try:
+ os.chdir(sugar.activity.activity.get_bundle_path())
+ except KeyError, err:
+ pass
+
+ try:
+ try:
+ try:
+ log.info( '''Running mainloop: %s''', fn )
+ fn()
+ except Exception, err:
+ log.error(
+ """Uncaught top-level exception: %s""",
+ util.get_traceback( err ),
+ )
+ raise
+ else:
+ log.info( "Mainloop exited" )
+ finally:
+ log.debug( "Clearing any pending events" )
+ from olpcgames import eventwrap
+ eventwrap.clear()
+ finally:
+ log.info( 'Main function finished, calling main_quit' )
+ gtk.main_quit()
+
+ source_object_id = None
+ def view_source(self):
+ """Implement the 'view source' key by saving
+ datastore, and then telling the Journal to view it."""
+ if self.source_object_id is None:
+ from sugar import profile
+ from sugar.datastore import datastore
+ from sugar.activity.activity import get_bundle_name, get_bundle_path
+ from gettext import gettext as _
+ import os.path
+ jobject = datastore.create()
+ metadata = {
+ 'title': _('%s Source') % get_bundle_name(),
+ 'title_set_by_user': '1',
+ 'suggested_filename': 'pippy_app.py',
+ 'icon-color': profile.get_color().to_string(),
+ 'mime_type': 'text/x-python',
+ }
+ for k,v in metadata.items():
+ jobject.metadata[k] = v # dict.update method is missing =(
+ jobject.file_path = os.path.join(get_bundle_path(), 'pippy_app.py')
+ datastore.write(jobject)
+ self.__source_object_id = jobject.object_id
+ jobject.destroy()
+ self.journal_show_object(self.__source_object_id)
+ def journal_show_object(self, object_id):
+ """Invoke journal_show_object from sugar.activity.activity if it
+ exists."""
+ try:
+ from sugar.activity.activity import show_object_in_journal
+ show_object_in_journal(object_id)
+ except ImportError:
+ pass # no love from sugar.
diff --git a/olpcgames/data/__init__.py b/olpcgames/data/__init__.py
new file mode 100755
index 0000000..8510186
--- /dev/null
+++ b/olpcgames/data/__init__.py
@@ -0,0 +1,36 @@
+"""Design-time __init__.py for resourcepackage
+
+This is the scanning version of __init__.py for your
+resource modules. You replace it with a blank or doc-only
+init when ready to release.
+"""
+try:
+ __file__
+except NameError:
+ pass
+else:
+ import os
+ if os.path.splitext(os.path.basename( __file__ ))[0] == "__init__":
+ try:
+ from resourcepackage import package, defaultgenerators
+ generators = defaultgenerators.generators.copy()
+
+ ### CUSTOMISATION POINT
+ ## import specialised generators here, such as for wxPython
+ #from resourcepackage import wxgenerators
+ #generators.update( wxgenerators.generators )
+ except ImportError:
+ pass
+ else:
+ package = package.Package(
+ packageName = __name__,
+ directory = os.path.dirname( os.path.abspath(__file__) ),
+ generators = generators,
+ )
+ package.scan(
+ ### CUSTOMISATION POINT
+ ## force true -> always re-loads from external files, otherwise
+ ## only reloads if the file is newer than the generated .py file.
+ # force = 1,
+ )
+
diff --git a/olpcgames/data/sleeping_svg.py b/olpcgames/data/sleeping_svg.py
new file mode 100755
index 0000000..c52398a
--- /dev/null
+++ b/olpcgames/data/sleeping_svg.py
@@ -0,0 +1,61 @@
+# -*- coding: ISO-8859-1 -*-
+"""Resource sleeping_svg (from file sleeping.svg)"""
+# written by resourcepackage: (1, 0, 1)
+source = 'sleeping.svg'
+package = 'olpcgames.data'
+data = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\012<svg\012\
+ xmlns=\"http://www.w3.org/2000/svg\"\012 xmlns:xlink=\"http:/\
+/www.w3.org/1999/xlink\"\012 width=\"737\"\012 height=\"923\"\012 ve\
+rsion=\"1.0\">\012 <defs>\012 <linearGradient\012 id=\"linearG\
+radient3152\">\012 <stop\012 style=\"stop-color:#b8ffb4\
+;stop-opacity:1;\"\012 offset=\"0\" />\012 <stop\012 \
+ offset=\"0.5\"\012 style=\"stop-color:#2eff22;stop-opaci\
+ty:0.5;\" />\012 <stop\012 style=\"stop-color:#ffffff;s\
+top-opacity:0;\"\012 offset=\"1\" />\012 </linearGradient>\
+\012 <radialGradient\012 xlink:href=\"#linearGradient3152\"\
+\012 id=\"radialGradient3158\"\012 cx=\"260\"\012 cy=\"2\
+35\"\012 fx=\"260\"\012 fy=\"235\"\012 r=\"259\"\012 gr\
+adientTransform=\"matrix(1,0,0,1.2531846,0,-59.560934)\"\012 \
+ gradientUnits=\"userSpaceOnUse\" />\012 </defs>\012 <g\012 tran\
+sform=\"translate(-3,-73)\">\012 <path\012 style=\"opacity:1\
+;color:#000000;fill:url(#radialGradient3158);fill-opacity:1;\
+fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-lineca\
+p:butt;stroke-linejoin:miter;marker:none;marker-start:none;m\
+arker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-da\
+sharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility\
+:visible;display:inline;overflow:visible\"\012 id=\"path217\
+8\"\012 d=\"M 519 235 A 259 324 0 1 1 0,235 A 259 324 0 1 \
+1 519 235 z\"\012 transform=\"matrix(1.4203822,0,0,1.42038\
+22,0,200)\" />\012 <path\012 style=\"fill:#000000;fill-opac\
+ity:0.75;fill-rule:nonzero;stroke:none;stroke-width:1pt;stro\
+ke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"\012 \
+ d=\"M 420,366 C 438,381 455,400 478,408 C 523,427 576,424 \
+620,405 C 632,400 644,393 655,387 C 652,389 638,397 649,391 \
+C 658,385 666,379 676,376 C 688,370 673,379 669,382 C 637,40\
+1 604,421 566,427 C 526,435 482,429 446,408 C 431,398 419,38\
+5 405,374 C 410,371 415,368 420,366 z \" />\012 <path\012 \
+style=\"fill:#000000;fill-opacity:0.75;fill-rule:nonzero;stro\
+ke:none;stroke-width:1pt;stroke-linecap:butt;stroke-linejoin\
+:miter;stroke-opacity:1\"\012 d=\"M 322,366 C 303,381 286,4\
+00 263,408 C 218,427 166,424 121,405 C 109,400 98,393 86,387\
+ C 89,389 103,397 93,391 C 84,385 75,379 65,376 C 53,370 68,\
+379 72,382 C 104,401 137,421 175,427 C 216,435 260,429 295,4\
+08 C 310,398 322,385 336,374 C 331,371 326,368 322,366 z \" /\
+>\012 <path\012 style=\"fill:#000000;fill-opacity:0.75;fil\
+l-rule:nonzero;stroke:none;stroke-width:1pt;stroke-linecap:b\
+utt;stroke-linejoin:miter;stroke-opacity:1\"\012 d=\"M 363,\
+383 C 347,418 353,458 345,495 C 339,525 324,551 312,579 C 30\
+4,598 298,620 309,639 C 317,655 335,667 353,669 C 379,671 40\
+5,664 429,653 C 442,646 405,667 423,656 C 429,652 434,647 44\
+1,645 C 455,639 439,650 434,653 C 408,669 378,679 347,679 C \
+327,679 308,667 297,651 C 285,634 287,613 294,594 C 302,570 \
+316,548 324,523 C 335,493 335,460 338,428 C 340,415 342,401 \
+349,390 C 353,388 358,385 363,383 z \" />\012 <path\012 st\
+yle=\"fill:#000000;fill-opacity:0.75;fill-rule:nonzero;stroke\
+:none;stroke-width:1pt;stroke-linecap:butt;stroke-linejoin:m\
+iter;stroke-opacity:1\"\012 d=\"M 206,735 C 245,737 285,740\
+ 324,744 C 357,745 391,746 424,744 C 468,738 510,723 550,703\
+ C 552,703 544,709 541,711 C 531,718 518,722 507,727 C 474,7\
+40 440,751 405,754 C 360,756 314,754 268,749 C 243,747 218,7\
+46 193,745 C 197,741 201,738 206,735 z \" />\012 </g>\012</svg>\012"
+### end
diff --git a/olpcgames/dbusproxy.py b/olpcgames/dbusproxy.py
new file mode 100755
index 0000000..a103e28
--- /dev/null
+++ b/olpcgames/dbusproxy.py
@@ -0,0 +1,93 @@
+"""Spike test for a safer networking system for DBUS-based objects"""
+from olpcgames import eventwrap, util
+from dbus import proxies
+import logging
+log = logging.getLogger( 'dbus' )
+log.setLevel( logging.DEBUG )
+
+def wrap( value, tube=None,path=None ):
+ """Wrap object with any required pygame-side proxies"""
+ if isinstance( value,proxies._ProxyMethod ):
+ return DBUSMethod( value, tube=tube, path=path )
+ elif isinstance( value, proxies._DeferredMethod ):
+ value._proxy_method = DBUSMethod( value._proxy_method, tube=tube, path=path )
+ return value
+ elif isinstance( value, proxies.ProxyObject ):
+ return DBUSProxy( value, tube=tube, path=path )
+ else:
+ return value
+
+class DBUSProxy( object ):
+ """Proxy for the DBUS Proxy object"""
+ def __init__( self, proxy, tube=None, path=None ):
+ log.info( 'Creating Pygame-side proxy for %s (%s)', proxy,path )
+ self.__proxy = proxy
+ self.__tube = tube
+ self.__path = path
+ def __getattr__( self, key ):
+ """Retrieve attribute of given key"""
+ from dbus import proxies
+ return wrap( getattr( self.__proxy, key ) )
+ def add_signal_receiver( self, callback, eventName, interface, path=None, sender_keyword='sender'):
+ """Add a new signal handler (which will be called many times) for given signal
+ """
+ log.info( """Setting signal receiver %s for event %s on interface %s (object path %s) with sender_keyword = %r""",
+ callback, eventName, interface, path, sender_keyword,
+ )
+ log.debug( """proxy: %s proxy.tube: %s""", self.__proxy, self.__proxy.tube )
+ self.__tube.add_signal_receiver(
+ Callback( callback ),
+ eventName,
+ interface,
+ path = path or self.__path,
+ sender_keyword = sender_keyword,
+ )
+
+class DBUSMethod( object ):
+ """DBUS method which does callbacks in the Pygame (eventwrapper) thread"""
+ def __init__( self, proxy, tube,path ):
+ log.info( 'Creating Pygame-side method proxy for %s', proxy )
+ self.__proxy = proxy
+ self.__tube = tube
+ self.__path = path
+ def __call__( self, *args, **named ):
+ """Perform the asynchronous call"""
+ log.info( 'Calling proxy for %s with *%s, **%s', self.__proxy, args, named )
+ callback, errback = named.get( 'reply_handler'), named.get( 'error_handler' )
+ if not callback:
+ raise TypeError( """Require a reply_handler named argument to do any asynchronous call""" )
+ else:
+ callback = Callback( callback )
+ if not errback:
+ errback = defaultErrback
+ else:
+ errback = Callback( errback )
+ named['reply_handler'] = callback
+ named['error_handler'] = errback
+ return self.__proxy( *args, **named )
+
+def defaultErrback( error ):
+ """Log the error to stderr/log"""
+ log.error( """Failure in DBUS call: %s""", error )
+
+class Callback( object ):
+ """PyGTK-side callback which generates a CallbackResult to process on the Pygame side"""
+ def __init__( self, callable, callContext = None):
+ """Initialize the callback to process results"""
+ self.callable = callable
+ if callContext is None:
+ callContext = util.get_traceback( None )
+ self.callContext = callContext
+ def __call__( self, *args, **named ):
+ """PyGTK-side callback operation"""
+ log.info( 'Callback %s return value *%s, **%s', self.callable, args, named )
+ from olpcgames import eventwrap
+ args = [wrap(a) for a in args]
+ named = dict([
+ (k,wrap(v)) for k,v in named.items()
+ ])
+ eventwrap.post(
+ eventwrap.CallbackResult(
+ self.callable, args, named, callContext = self.callContext
+ )
+ )
diff --git a/olpcgames/eventwrap.py b/olpcgames/eventwrap.py
new file mode 100755
index 0000000..402109c
--- /dev/null
+++ b/olpcgames/eventwrap.py
@@ -0,0 +1,388 @@
+"""Provides substitute for Pygame's "event" module using gtkEvent
+
+Provides methods which will be substituted into Pygame in order to
+provide the synthetic events that we will feed into the Pygame queue.
+These methods are registered by the "install" method.
+
+This event queue does not support getting events only of a certain type.
+You need to get all pending events at a time, or filter them yourself. You
+can, however, block and unblock events of certain types, so that may be
+useful to you.
+
+Set_grab doesn't do anything (you are not allowed to grab events). Sorry.
+
+Extensions:
+
+ wait( timeout=None ) -- allows you to wait for only a specified period
+ before you return to the application. Can be used to e.g. wait for a
+ short period, then release some resources, then wait a bit more, then
+ release a few more resources, then a bit more...
+"""
+import pygame
+import gtk
+import Queue
+import thread, threading
+import logging
+from olpcgames import util
+
+log = logging.getLogger( 'olpcgames.eventwrap' )
+
+from pygame.event import Event, event_name, pump as pygame_pump, get as pygame_get
+
+class Event(object):
+ """Mock pygame events"""
+ def __init__(self, type, dict=None,**named):
+ """Initialise the new event variables from dictionary and named become attributes"""
+ self.type = type
+ if dict:
+ self.__dict__.update( dict )
+ self.__dict__.update( named )
+ def _get_dict( self ):
+ return self.__dict__
+ dict = property( _get_dict )
+ def __repr__( self ):
+ result = []
+ for key,value in self.__dict__.items():
+ if not key.startswith( '_' ):
+ result.append( '%s = %r'%( key, value ))
+ return '%s( %s, %s )'%(
+ self.__class__.__name__,
+ self.type,
+ ",".join( result ),
+ )
+ def block( self ):
+ """Block until this event is finished processing
+
+ Event process is only finalized on the *next* call to retrieve an event
+ after the processing operation in which the event is processed. In some
+ extremely rare cases we might actually see that happen, were the
+ file-saving event (for example) causes the Pygame event loop to exit.
+ In that case, the GTK event loop *could* hang.
+ """
+ log.info( '''Blocking GTK thread on event: %s''', self )
+ self.__lock = threading.Event()
+ self.__lock.wait()
+ def retire( self ):
+ """Block the GTK event loop until this event is processed"""
+ try:
+ self.__lock.set()
+ log.info( '''Released GTK thread on event: %s''', self )
+ except AttributeError, err:
+ pass
+
+class CallbackResult( object ):
+ def __init__( self, callable, args, named, callContext=None ):
+ """Perform callback in Pygame loop with args and named
+
+ callContext is used to provide more information when there is
+ a failure in the callback (for debugging purposes)
+ """
+ self.callable = callable
+ self.args = args
+ self.named = named
+ if callContext is None:
+ callContext = util.get_traceback( None )
+ self.callContext = callContext
+ def __call__( self ):
+ """Perform the actual callback in the Pygame event loop"""
+ try:
+ self.callable( *self.args, **self.named )
+ except Exception, err:
+ log.error(
+ """Failure in callback %s( *%s, **%s ): %s\n%s""",
+ getattr(self.callable, '__name__',self.callable),
+ self.args, self.named,
+ util.get_traceback( err ),
+ self.callContext
+ )
+
+
+_EVENTS_TO_RETIRE = []
+
+def _releaseEvents( ):
+ """Release/retire previously-processed events"""
+ if _EVENTS_TO_RETIRE:
+ for event in _EVENTS_TO_RETIRE:
+ try:
+ event.retire()
+ except AttributeError, err:
+ pass
+
+def _processCallbacks( events ):
+ """Process any callbacks in events and remove from the stream"""
+ result = []
+ for event in events:
+ if isinstance( event, CallbackResult ):
+ event()
+ else:
+ result.append( event )
+ if events and not result:
+ result.append(
+ Event( type=pygame.NOEVENT )
+ )
+ return result
+
+def _recordEvents( events ):
+ """Record the set of events to retire on the next iteration"""
+ global _EVENTS_TO_RETIRE
+ events = _processCallbacks( events )
+ _EVENTS_TO_RETIRE = events
+ return events
+
+def install():
+ """Installs this module (eventwrap) as an in-place replacement for the pygame.event module.
+
+ Use install() when you need to interact with Pygame code written
+ without reference to the olpcgames wrapper mechanisms to have the
+ code use this module's event queue.
+
+ XXX Really, use it everywhere you want to use olpcgames, as olpcgames
+ registers the handler itself, so you will always wind up with it registered when
+ you use olpcgames (the gtkEvent.Translator.hook_pygame method calls it).
+ """
+ log.info( 'Installing OLPCGames event wrapper' )
+ from olpcgames import eventwrap
+ import pygame
+ pygame.event = eventwrap
+ import sys
+ sys.modules["pygame.event"] = eventwrap
+
+# Event queue:
+class _FilterQueue( Queue.Queue ):
+ """Simple Queue sub-class with a put_left method"""
+ def get_type( self, filterFunction, block=True, timeout=None ):
+ """Get events of a given type
+
+ Note: can raise Empty *even* when blocking if someone else
+ pops the event off the queue before we get around to it.
+ """
+ self.not_empty.acquire()
+ try:
+ if not block:
+ if self._empty_type( filterFunction ):
+ raise Queue.Empty
+ elif timeout is None:
+ while self._empty_type( filterFunction ):
+ self.not_empty.wait()
+ else:
+ if timeout < 0:
+ raise ValueError("'timeout' must be a positive number")
+ endtime = _time() + timeout
+ while self._empty_type( filterFunction ):
+ remaining = endtime - _time()
+ if remaining <= 0.0:
+ raise Queue.Empty
+ self.not_empty.wait(remaining)
+ item = self._get_type( filterFunction )
+ self.not_full.notify()
+ return item
+ finally:
+ self.not_empty.release()
+ def _empty_type( self, filterFunction ):
+ """Are we empty with respect to filterFunction?"""
+ for element in self.queue:
+ if filterFunction( element ):
+ return False
+ return True
+ def _get_type( self, filterFunction ):
+ """Get the first instance which matches filterFunction"""
+ for element in self.queue:
+ if filterFunction( element ):
+ self.queue.remove( element )
+ return element
+ # someone popped the event off the queue before we got to it!
+ raise Queue.Empty
+ def peek_type( self, filterFunction= lambda x: True ):
+ """Peek to see if we have filterFunction-matching element
+
+ Note: obviously this is *not* thread safe, it's just informative...
+ """
+ try:
+ for element in self.queue:
+ if filterFunction( element ):
+ return element
+ return None
+ except RuntimeError, err:
+ return None # none yet, at least
+
+g_events = _FilterQueue()
+
+# Set of blocked events as set by set
+g_blocked = set()
+g_blockedlock = thread.allocate_lock() # should use threading instead
+g_blockAll = False
+
+def _typeChecker( types ):
+ """Create check whether an event is in types"""
+ try:
+ if 1 in types:
+ pass
+ def check( element ):
+ return element.type in types
+ return check
+ except TypeError, err:
+ def check( element ):
+ return element.type == types
+ return check
+
+def pump():
+ """Handle any window manager and other external events that aren't passed to the user
+
+ Call this periodically (once a frame) if you don't call get(), poll() or wait()
+ """
+ pygame_pump()
+ _releaseEvents()
+
+def get( types=None):
+ """Get a list of all pending events
+
+ types -- either an integer event-type or a sequence of integer event types
+ which restrict the set of event-types returned from the queue. Keep in mind
+ that if you do not remove events you may wind up with an eternally growing
+ queue or a full queue. Normally you will want to remove all events in your
+ top-level event-loop and propagate them yourself.
+
+ Note: if you use types you lose all event ordering guarantees, events
+ may show up after events which were originally produced before them due to
+ the re-ordering of the queue on filtering!
+ """
+ pump()
+ eventlist = []
+ try:
+ if types:
+ check = _typeChecker( types )
+ while True:
+ eventlist.append(g_events.get_type( check, block=False))
+ else:
+ while True:
+ eventlist.append(g_events.get(block=False))
+ except Queue.Empty:
+ pass
+
+ pygameEvents = pygame_get()
+ if pygameEvents:
+ log.info( 'Raw Pygame events: %s', pygameEvents)
+ eventlist.extend( pygameEvents )
+ return _recordEvents( eventlist )
+
+def poll():
+ """Get the next pending event if exists. Otherwise, return pygame.NOEVENT."""
+ pump()
+ try:
+ result = g_events.get(block=False)
+ return _recordEvents( [result] )[0]
+ except Queue.Empty:
+ return Event(pygame.NOEVENT)
+
+
+def wait( timeout = None):
+ """Get the next pending event, wait up to timeout if none
+
+ timeout -- if present, only wait up to timeout seconds, if we
+ do not find an event before then, return None. timeout
+ is an OLPCGames-specific extension.
+ """
+ pump()
+ try:
+ result = None
+ result = g_events.get(block=True, timeout=timeout)
+ try:
+ return _recordEvents( [result] )[0]
+ except IndexError, err:
+ return Event( type=pygame.NOEVENT )
+ except Queue.Empty, err:
+ return None
+
+def peek(types=None):
+ """True if there is any pending event
+
+ types -- optional set of event-types used to check whether
+ an event is of interest. If specified must be either a sequence
+ of integers/longs or an integer/long.
+ """
+ if types:
+ check = _typeChecker( types )
+ return g_events.peek_type( check ) is not None
+ return not g_events.empty()
+
+def clear():
+ """Clears the entire pending queue of events
+
+ Rarely used
+ """
+ try:
+ discarded = []
+ while True:
+ discarded.append( g_events.get(block=False) )
+ discarded = _recordEvents( discarded )
+ _releaseEvents()
+ return discarded
+ except Queue.Empty:
+ pass
+
+def set_blocked(item):
+ """Block item/items from being added to the event queue"""
+ g_blockedlock.acquire()
+ try:
+ # FIXME: we do not currently know how to block all event types when
+ # you set_blocked(none).
+ [g_blocked.add(x) for x in makeseq(item)]
+ finally:
+ g_blockedlock.release()
+
+def set_allowed(item):
+ """Allow item/items to be added to the event queue"""
+ g_blockedlock.acquire()
+ try:
+ if item is None:
+ # Allow all events when you set_allowed(none). Strange, eh?
+ # Pygame is a wonderful API.
+ g_blocked.clear()
+ else:
+ [g_blocked.remove(x) for x in makeseq(item)]
+ finally:
+ g_blockedlock.release()
+
+def get_blocked(*args, **kwargs):
+ g_blockedlock.acquire()
+ try:
+ blocked = frozenset(g_blocked)
+ return blocked
+ finally:
+ g_blockedlock.release()
+
+def set_grab(grabbing):
+ """This method will not be implemented"""
+
+def get_grab():
+ """This method will not be implemented"""
+
+def post(event):
+ """Post a new event to the Queue of events"""
+ g_blockedlock.acquire()
+ try:
+ if getattr(event,'type',None) not in g_blocked:
+ g_events.put(event, block=False)
+ finally:
+ g_blockedlock.release()
+
+def makeseq(obj):
+ """Accept either a scalar object or a sequence, and return a sequence
+ over which we can iterate. If we were passed a sequence, return it
+ unchanged. If we were passed a scalar, return a tuple containing only
+ that scalar. This allows the caller to easily support one-or-many.
+ """
+ # Strings are the exception because you can iterate over their chars
+ # -- yet, for all the purposes I've ever cared about, I want to treat
+ # a string as a scalar.
+ if isinstance(obj, basestring):
+ return (obj,)
+ try:
+ # Except as noted above, if you can get an iter() from an object,
+ # it's a collection.
+ iter(obj)
+ return obj
+ except TypeError:
+ # obj is a scalar. Wrap it in a tuple so we can iterate over the
+ # one item.
+ return (obj,)
diff --git a/olpcgames/gtkEvent.py b/olpcgames/gtkEvent.py
new file mode 100755
index 0000000..6b20102
--- /dev/null
+++ b/olpcgames/gtkEvent.py
@@ -0,0 +1,289 @@
+"""gtkEvent.py: translate GTK events into Pygame events."""
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+import pygame
+from olpcgames import eventwrap
+import logging
+log = logging.getLogger( 'olpcgames.gtkevent' )
+##log.setLevel( logging.DEBUG )
+
+class _MockEvent(object):
+ """Used to inject key-repeat events on the gtk side."""
+ def __init__(self, keyval):
+ self.keyval = keyval
+
+class Translator(object):
+ """Utility class to translate GTK events into Pygame events
+
+ The Translator object interprets incoming GTK events and generates
+ Pygame events in the eventwrap module's queue as a result.
+ It also handles generating Pygame style key-repeat events
+ by synthesizing them via a GTK timer.
+ """
+ key_trans = {
+ 'Alt_L': pygame.K_LALT,
+ 'Alt_R': pygame.K_RALT,
+ 'Control_L': pygame.K_LCTRL,
+ 'Control_R': pygame.K_RCTRL,
+ 'Shift_L': pygame.K_LSHIFT,
+ 'Shift_R': pygame.K_RSHIFT,
+ 'Super_L': pygame.K_LSUPER,
+ 'Super_R': pygame.K_RSUPER,
+ 'KP_Page_Up' : pygame.K_KP9,
+ 'KP_Page_Down' : pygame.K_KP3,
+ 'KP_End' : pygame.K_KP1,
+ 'KP_Home' : pygame.K_KP7,
+ 'KP_Up' : pygame.K_KP8,
+ 'KP_Down' : pygame.K_KP2,
+ 'KP_Left' : pygame.K_KP4,
+ 'KP_Right' : pygame.K_KP6,
+
+ }
+
+ mod_map = {
+ pygame.K_LALT: pygame.KMOD_LALT,
+ pygame.K_RALT: pygame.KMOD_RALT,
+ pygame.K_LCTRL: pygame.KMOD_LCTRL,
+ pygame.K_RCTRL: pygame.KMOD_RCTRL,
+ pygame.K_LSHIFT: pygame.KMOD_LSHIFT,
+ pygame.K_RSHIFT: pygame.KMOD_RSHIFT,
+ }
+
+ def __init__(self, mainwindow, mouselistener=None):
+ """Initialise the Translator with the windows to which to listen"""
+ # _inner_evb is Mouselistener
+ self._mainwindow = mainwindow
+ if mouselistener is None:
+ mouselistener = mainwindow
+
+ self._inner_evb = mouselistener
+
+ # Need to set our X event masks so we see mouse motion and stuff --
+ mainwindow.set_events(
+ gtk.gdk.KEY_PRESS_MASK | \
+ gtk.gdk.KEY_RELEASE_MASK \
+ )
+
+ self._inner_evb.set_events(
+ gtk.gdk.POINTER_MOTION_MASK | \
+ gtk.gdk.POINTER_MOTION_HINT_MASK | \
+ gtk.gdk.BUTTON_MOTION_MASK | \
+ gtk.gdk.BUTTON_PRESS_MASK | \
+ gtk.gdk.BUTTON_RELEASE_MASK
+ )
+
+ # Callback functions to link the event systems
+ mainwindow.connect('unrealize', self._quit)
+ mainwindow.connect('key_press_event', self._keydown)
+ mainwindow.connect('key_release_event', self._keyup)
+ self._inner_evb.connect('button_press_event', self._mousedown)
+ self._inner_evb.connect('button_release_event', self._mouseup)
+ self._inner_evb.connect('motion-notify-event', self._mousemove)
+
+ # You might need to do this
+ mainwindow.set_flags(gtk.CAN_FOCUS)
+ self._inner_evb.set_flags(gtk.CAN_FOCUS)
+
+ # Internal data
+ self.__stopped = False
+ self.__keystate = [0] * 323
+ self.__button_state = [0,0,0]
+ self.__mouse_pos = (0,0)
+ self.__repeat = (None, None)
+ self.__held = set()
+ self.__held_time_left = {}
+ self.__held_last_time = {}
+ self.__tick_id = None
+
+ #print "translator initialized"
+ self._inner_evb.connect( 'expose-event', self.do_expose_event )
+# screen = gtk.gdk.screen_get_default()
+# screen.connect( 'size-changed', self.do_resize_event )
+ self._inner_evb.connect( 'configure-event', self.do_resize_event )
+ def do_expose_event(self, event, widget):
+ """Handle exposure event (trigger redraw by gst)"""
+ log.info( 'Expose event: %s', event )
+ from olpcgames import eventwrap
+ eventwrap.post( eventwrap.Event( eventwrap.pygame.VIDEOEXPOSE ))
+ return True
+ def do_resize_event( self, activity, event ):
+ """Our screen (actually, the default screen) has resized"""
+ log.info( 'Resize event: %s %s', activity, event )
+ log.info( 'Event values: %s', (event.width,event.height) )
+# from olpcgames import eventwrap
+# # shouldn't the activity's window have this information too?
+# eventwrap.post(
+# eventwrap.Event(
+# eventwrap.pygame.VIDEORESIZE,
+# dict(size=(event.width,event.height), width=event.width, height=event.height)
+# )
+# )
+ return False # continue processing
+ def hook_pygame(self):
+ """Hook the various Pygame features so that we implement the event APIs"""
+ # Pygame should be initialized. Hijack their key and mouse methods
+ pygame.key.get_pressed = self._get_pressed
+ pygame.key.set_repeat = self._set_repeat
+ pygame.mouse.get_pressed = self._get_mouse_pressed
+ pygame.mouse.get_pos = self._get_mouse_pos
+ import eventwrap
+ eventwrap.install()
+
+ def _quit(self, data=None):
+ self.__stopped = True
+ eventwrap.post(eventwrap.Event(pygame.QUIT))
+
+ def _keydown(self, widget, event):
+ key = event.keyval
+ log.debug( 'key down: %s', key )
+ if key in self.__held:
+ return True
+ else:
+ if self.__repeat[0] is not None:
+ self.__held_last_time[key] = pygame.time.get_ticks()
+ self.__held_time_left[key] = self.__repeat[0]
+ self.__held.add(key)
+
+ return self._keyevent(widget, event, pygame.KEYDOWN)
+
+ def _keyup(self, widget, event):
+ key = event.keyval
+ if self.__repeat[0] is not None:
+ if key in self.__held:
+ # This is possibly false if set_repeat() is called with a key held
+ del self.__held_time_left[key]
+ del self.__held_last_time[key]
+ self.__held.discard(key)
+
+ return self._keyevent(widget, event, pygame.KEYUP)
+
+ def _keymods(self):
+ """Extract the keymods as they stand currently."""
+ mod = 0
+ for key_val, mod_val in self.mod_map.iteritems():
+ mod |= self.__keystate[key_val] and mod_val
+ return mod
+
+
+ def _keyevent(self, widget, event, type):
+ key = gtk.gdk.keyval_name(event.keyval)
+ if key is None:
+ # No idea what this key is.
+ return False
+
+ keycode = None
+ if key in self.key_trans:
+ keycode = self.key_trans[key]
+ elif hasattr(pygame, 'K_'+key.upper()):
+ keycode = getattr(pygame, 'K_'+key.upper())
+ elif hasattr(pygame, 'K_'+key.lower()):
+ keycode = getattr(pygame, 'K_'+key.lower())
+ elif key == 'XF86Start':
+ # view source request, specially handled...
+ self._mainwindow.view_source()
+ else:
+ print 'Key %s unrecognized'%key
+
+ if keycode is not None:
+ if type == pygame.KEYDOWN:
+ mod = self._keymods()
+ self.__keystate[keycode] = type == pygame.KEYDOWN
+ if type == pygame.KEYUP:
+ mod = self._keymods()
+ ukey = unichr(gtk.gdk.keyval_to_unicode(event.keyval))
+ if ukey == '\000':
+ ukey = ''
+ evt = eventwrap.Event(type, key=keycode, unicode=ukey, mod=mod)
+ assert evt.key, evt
+ self._post(evt)
+ return True
+
+ def _get_pressed(self):
+ """Retrieve map/array of which keys are currently depressed (held down)"""
+ return self.__keystate
+
+ def _get_mouse_pressed(self):
+ """Return three-element array of which mouse-buttons are currently depressed (held down)"""
+ return self.__button_state
+
+ def _mousedown(self, widget, event):
+ self.__button_state[event.button-1] = 1
+ return self._mouseevent(widget, event, pygame.MOUSEBUTTONDOWN)
+
+ def _mouseup(self, widget, event):
+ self.__button_state[event.button-1] = 0
+ return self._mouseevent(widget, event, pygame.MOUSEBUTTONUP)
+
+ def _mouseevent(self, widget, event, type):
+
+ evt = eventwrap.Event(type,
+ button=event.button,
+ pos=(event.x, event.y))
+ self._post(evt)
+ return True
+
+ def _mousemove(self, widget, event):
+ # From http://www.learningpython.com/2006/07/25/writing-a-custom-widget-using-pygtk/
+ # if this is a hint, then let's get all the necessary
+ # information, if not it's all we need.
+ if event.is_hint:
+ x, y, state = event.window.get_pointer()
+ else:
+ x = event.x
+ y = event.y
+ state = event.state
+
+ rel = (x - self.__mouse_pos[0],
+ y - self.__mouse_pos[1])
+ self.__mouse_pos = (x, y)
+
+ self.__button_state = [
+ state & gtk.gdk.BUTTON1_MASK and 1 or 0,
+ state & gtk.gdk.BUTTON2_MASK and 1 or 0,
+ state & gtk.gdk.BUTTON3_MASK and 1 or 0,
+ ]
+
+ evt = eventwrap.Event(pygame.MOUSEMOTION,
+ pos=self.__mouse_pos,
+ rel=rel,
+ buttons=self.__button_state)
+ self._post(evt)
+ return True
+
+ def _tick(self):
+ """Generate synthetic events for held-down keys"""
+ cur_time = pygame.time.get_ticks()
+ for key in self.__held:
+ delta = cur_time - self.__held_last_time[key]
+ self.__held_last_time[key] = cur_time
+
+ self.__held_time_left[key] -= delta
+ if self.__held_time_left[key] <= 0:
+ self.__held_time_left[key] = self.__repeat[1]
+ self._keyevent(None, _MockEvent(key), pygame.KEYDOWN)
+
+ return True
+
+ def _set_repeat(self, delay=None, interval=None):
+ """Set the key-repetition frequency for held-down keys"""
+ if delay is not None and self.__repeat[0] is None:
+ self.__tick_id = gobject.timeout_add(10, self._tick)
+ elif delay is None and self.__repeat[0] is not None:
+ gobject.source_remove(self.__tick_id)
+ self.__repeat = (delay, interval)
+
+ def _get_mouse_pos(self):
+ """Retrieve the current mouse position as a two-tuple of integers"""
+ return self.__mouse_pos
+
+ def _post(self, evt):
+ try:
+ eventwrap.post(evt)
+ except pygame.error, e:
+ if str(e) == 'Event queue full':
+ print "Event queue full!"
+ pass
+ else:
+ raise e
diff --git a/olpcgames/mesh.py b/olpcgames/mesh.py
new file mode 100755
index 0000000..0d75c18
--- /dev/null
+++ b/olpcgames/mesh.py
@@ -0,0 +1,584 @@
+'''Utilities for wrapping the telepathy network for Pygame
+
+The 'mesh' module allows your Pygame game to be Shared
+across the OLPC networking infrastructure (D-bus and Tubes).
+It offers a simplified view of the Telepathy system.
+
+All Sugar activities have a 'Share' menu (toolbar) which is
+intended to allow other people to join the activity instance
+and collaborate with you. When you select Share, the activity's
+icon appears on the Neighborhood view of other laptops.
+
+If you do nothing else with networking, this is all that will
+happen: if anyone selects your shared activity icon, they will
+just spawn a new instance of the activity, and they will get to
+play your game alone.
+
+The mesh module automatically sets up a connection from each
+participant to every other participant. It provides (string based)
+communications channels that let you either broadcast messages
+to other users or communicate point-to-point to one other user.
+
+You can use the "handles" which uniquely idenify users to send
+messages to an individual user (send_to( handle, message )) or
+broadcast( message ) to send a message to all participants.
+
+More advanced (structured) networking can be handled by using
+the get_object( handle, path ) function, which looks up an object
+(by DBUS path) shared by the user "handle" and returns a
+DBUS/Telepathy proxy for that object. The object you get back is
+actually an olpcgames.dbusproxy.DBUSProxy instance, which
+enforces asynchronous operations and runs your
+reply_handler/error_handler in the Pygame event loop.
+
+NOTE:
+ You *cannot* make synchronous calls on these objects!
+ You must use the named arguments:
+
+ reply_handler, error_handler
+
+ for every call which you perform on a shared object (normally
+ these are ExportedGObject instances).
+
+If you want to run your callbacks in the GTK event loop (for instance
+because they need to handle GTK-side objects), you can use the
+dbus_get_object function. This is *not* recommended for normal
+usage, as any call to Pygame operations within the GTK event loop
+can cause a segfault/core of your entire Activity.
+
+Note:
+
+ mesh sets up N**2 connections for each shared activity, obviously
+ that will not scale to very large shared activities.
+
+Note:
+
+ The intention is that mesh will be refactored, possibly as a
+ new module called "olpcgames.network", which would break out
+ the various components so that there is no longer an assumed
+ networking layout. We will attempt to retain the mesh module's
+ API as we do so.
+
+Events produced:
+
+ olpcgames.CONNECT -- The tube connection was started. (i.e., the
+ user clicked Share or started the activity from the Neighborhood
+ screen).
+
+ Event properties:
+
+ id -- a unique identifier for this connection. (shouldn't be needed
+ for anything)
+
+ olpcgames.PARTICIPANT_ADD -- A participant joined the activity.
+ This will trigger for the local user as well as any arriving remote
+ users. Note that this *only* occurs after the activity is shared,
+ that is, the local user does not appear until after they have
+ shared a locally-started activity.
+
+ Event properties:
+
+ handle -- the arriving user's handle (a uniquely identifying string
+ assigned to the user by the Telepathy system, not human
+ readable), see lookup_buddy to retrieve human-readable
+ descriptions of the user.
+
+ olpcgames.PARTICIPANT_REMOVE -- A participant quit the activity.
+
+ Event properties:
+
+ handle -- the departing user's handle.
+
+ olpcgames.MESSAGE_UNI -- A message was sent to you.
+
+ Event properties:
+
+ content -- the content of the message (a string)
+ handle -- the handle of the sending user.
+
+ olpcgames.MESSAGE_MULTI -- A message was sent to everyone.
+
+ Event properties:
+
+ content -- the content of the message (a string)
+ handle -- the handle of the sending user.
+
+Note:
+
+ Eventually we will stop using top-level Pygame event types for the
+ various networking message types (currently four of them). We will
+ likely use UserEvent with a sub-type specifier for the various events
+ that OLPCGames produces.
+
+See Also:
+
+ http://blog.vrplumber.com/2016 -- Discussion of how Productive uses
+ the mesh module and raw Telepathy (ExportedGObject instances)
+'''
+import logging
+log = logging.getLogger( 'olpcgames.mesh' )
+##log.setLevel( logging.DEBUG )
+import olpcgames
+from olpcgames.util import get_traceback
+try:
+ from sugar.presence.tubeconn import TubeConnection
+except ImportError, err:
+ TubeConnection = object
+try:
+ from dbus.gobject_service import ExportedGObject
+except ImportError, err:
+ ExportedGObject = object
+from dbus.service import method, signal
+
+try:
+ import telepathy
+except ImportError, err:
+ telepathy = None
+
+try:
+ import sugar.presence.presenceservice
+except Exception, err:
+ pass
+import pygame.event as PEvent
+
+class OfflineError( Exception ):
+ """Raised when we cannot complete an operation due to being offline"""
+
+DBUS_IFACE="org.laptop.games.pygame"
+DBUS_PATH="/org/laptop/games/pygame"
+DBUS_SERVICE = None
+
+
+### NEW PYGAME EVENTS ###
+
+CONNECT = olpcgames.CONNECT
+PARTICIPANT_ADD = olpcgames.PARTICIPANT_ADD
+PARTICIPANT_REMOVE = olpcgames.PARTICIPANT_REMOVE
+MESSAGE_UNI = olpcgames.MESSAGE_UNI
+MESSAGE_MULTI = olpcgames.MESSAGE_MULTI
+
+
+# Private objects for useful purposes!
+pygametubes = []
+text_chan, tubes_chan = (None, None)
+conn = None
+initiating = False
+joining = False
+
+connect_callback = None
+
+def is_initiating():
+ '''A version of is_initiator that's a bit less goofy, and can be used
+ before the Tube comes up.'''
+ global initiating
+ return initiating
+
+def is_joining():
+ '''Returns True if the activity was started up by means of the
+ Neighbourhood mesh view.'''
+ global joining
+ return joining
+
+def set_connect_callback(cb):
+ '''Just the same as the Pygame event loop can listen for CONNECT,
+ this is just an ugly callback that the glib side can use to be aware
+ of when the Tube is ready.'''
+ global connect_callback
+ connect_callback = cb
+
+def activity_shared(activity):
+ '''Called when the user clicks Share.'''
+
+ global initiating
+ initiating = True
+
+ _setup(activity)
+
+
+ log.debug('This is my activity: making a tube...')
+ channel = tubes_chan[telepathy.CHANNEL_TYPE_TUBES]
+ if hasattr( channel, 'OfferDBusTube' ):
+ id = channel.OfferDBusTube(
+ DBUS_SERVICE, {})
+ else:
+ id = channel.OfferTube(
+ telepathy.TUBE_TYPE_DBUS, DBUS_SERVICE, {})
+
+ global connect_callback
+ if connect_callback is not None:
+ connect_callback()
+
+def activity_joined(activity):
+ '''Called at the startup of our Activity, when the user started it via Neighborhood intending to join an existing activity.'''
+
+ # Find out who's already in the shared activity:
+ log.debug('Joined an existing shared activity')
+
+ for buddy in activity._shared_activity.get_joined_buddies():
+ log.debug('Buddy %s is already in the activity' % buddy.props.nick)
+
+
+ global initiating
+ global joining
+ initiating = False
+ joining = True
+
+
+ _setup(activity)
+
+ tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
+ reply_handler=_list_tubes_reply_cb,
+ error_handler=_list_tubes_error_cb)
+
+ global connect_callback
+ if connect_callback is not None:
+ connect_callback()
+
+def _getConn( activity ):
+ log.debug( '_getConn' )
+ global conn
+ if conn:
+ return conn
+ else:
+ if hasattr( activity._shared_activity, 'telepathy_conn' ):
+ log.debug( '''new-style api for retrieving telepathy connection present''' )
+ conn = activity._shared_activity.telepathy_conn
+ else:
+ pservice = _get_presence_service()
+ log.debug( '_get_presence_service -> %s', pservice )
+ name, path = pservice.get_preferred_connection()
+ log.debug( '_get_presence_service -> %s, %s', name, path)
+ conn = telepathy.client.Connection(name, path)
+ log.debug( 'Telepathy Client Connection: %s', conn )
+ return conn
+
+
+
+def _setup(activity):
+ '''Determines text and tube channels for the current Activity. If no tube
+channel present, creates one. Updates text_chan and tubes_chan.
+
+setup(sugar.activity.Activity, telepathy.client.Connection)'''
+ global text_chan, tubes_chan, DBUS_SERVICE
+ log.info( 'Setup for %s', activity )
+ if not DBUS_SERVICE:
+ DBUS_SERVICE = activity.get_bundle_id()
+ if not activity.get_shared():
+ log.error('Failed to share or join activity')
+ raise "Failure"
+
+ if hasattr( activity._shared_activity, 'telepathy_tubes_chan' ):
+ log.debug( '''Improved channel setup API available''' )
+ _getConn( activity )
+ conn = activity._shared_activity.telepathy_conn
+ tubes_chan = activity._shared_activity.telepathy_tubes_chan
+ text_chan = activity._shared_activity.telepathy_text_chan
+ else:
+ log.debug( '''Old-style setup API''' )
+ bus_name, conn_path, channel_paths = activity._shared_activity.get_channels()
+ _getConn( activity )
+
+ # Work out what our room is called and whether we have Tubes already
+ room = None
+ tubes_chan = None
+ text_chan = None
+ for channel_path in channel_paths:
+ log.debug( 'Testing channel path: %s', channel_path)
+ channel = telepathy.client.Channel(bus_name, channel_path)
+ htype, handle = channel.GetHandle()
+ log.debug( ' Handle Type: %s Handle: %s', htype, handle)
+ if htype == telepathy.HANDLE_TYPE_ROOM:
+ log.debug('Found our room: it has handle#%d "%s"',
+ handle, conn.InspectHandles(htype, [handle])[0])
+ room = handle
+ ctype = channel.GetChannelType()
+ if ctype == telepathy.CHANNEL_TYPE_TUBES:
+ log.debug('Found our Tubes channel at %s', channel_path)
+ tubes_chan = channel
+ elif ctype == telepathy.CHANNEL_TYPE_TEXT:
+ log.debug('Found our Text channel at %s', channel_path)
+ text_chan = channel
+
+ if room is None:
+ log.error("Presence service didn't create a room")
+ raise "Failure"
+ if text_chan is None:
+ log.error("Presence service didn't create a text channel")
+ raise "Failure"
+
+ # Make sure we have a Tubes channel - PS doesn't yet provide one
+ if tubes_chan is None:
+ log.debug("Didn't find our Tubes channel, requesting one...")
+ tubes_chan = conn.request_channel(telepathy.CHANNEL_TYPE_TUBES,
+ telepathy.HANDLE_TYPE_ROOM, room, True)
+
+ tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube',
+ new_tube_cb)
+
+ log.info( 'Setup for %s complete', activity )
+ return (text_chan, tubes_chan)
+
+def new_tube_cb(id, initiator, type, service, params, state):
+ log.debug("New_tube_cb called: %s %s %s" % (id, initiator, type))
+ if (type == telepathy.TUBE_TYPE_DBUS and service == DBUS_SERVICE):
+ if state == telepathy.TUBE_STATE_LOCAL_PENDING:
+ channel = tubes_chan[telepathy.CHANNEL_TYPE_TUBES]
+ if hasattr( channel, 'AcceptDBusTube' ):
+ channel.AcceptDBusTube( id )
+ else:
+ channel.AcceptTube(id)
+
+ tube_conn = TubeConnection(conn,
+ tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
+ id, group_iface=text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
+
+ global pygametubes, initiating
+ pygametubes.append(PygameTube(tube_conn, initiating, len(pygametubes)))
+
+
+def _list_tubes_reply_cb(tubes):
+ for tube_info in tubes:
+ new_tube_cb(*tube_info)
+
+def _list_tubes_error_cb(e):
+ log.error('ListTubes() failed: %s', e)
+
+def lookup_buddy( dbus_handle, callback, errback=None ):
+ """Do a lookup on the buddy information, callback with the information
+
+ Calls callback( buddy ) with the result of the lookup, or errback( error ) with
+ a dbus description of the error in the lookup process.
+
+ returns None
+ """
+ log.debug('Trying to find owner of handle %s...', dbus_handle)
+ cs_handle = instance().tube.bus_name_to_handle[dbus_handle]
+ log.debug('Trying to find my handle in %s...', cs_handle)
+ group = text_chan[telepathy.CHANNEL_INTERFACE_GROUP]
+ log.debug( 'Calling GetSelfHandle' )
+ if not errback:
+ def errback( error ):
+ log.error( """Failure retrieving handle for buddy lookup: %s""", error )
+ def with_my_csh( my_csh ):
+ log.debug('My handle in that group is %s', my_csh)
+ def _withHandle( handle ):
+ """process the results of the handle values"""
+ # XXX: we're assuming that we have Buddy objects for all contacts -
+ # this might break when the server becomes scalable.
+ pservice = _get_presence_service()
+ name, path = pservice.get_preferred_connection()
+ callback( pservice.get_buddy_by_telepathy_handle(name, path, handle) )
+ if my_csh == cs_handle:
+ conn.GetSelfHandle(reply_handler = _withHandle, error_handler=errback)
+ log.debug('CS handle %s belongs to me, looking up with GetSelfHandle', cs_handle)
+ elif group.GetGroupFlags() & telepathy.CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
+ handle = group.GetHandleOwners([cs_handle])[0]
+ log.debug('CS handle %s belongs to %s', cs_handle, handle)
+ _withHandle( handle )
+ else:
+ handle = cs_handle
+ log.debug('non-CS handle %s belongs to itself', handle)
+ _withHandle( handle )
+ group.GetSelfHandle( reply_handler = with_my_csh, error_handler = errback)
+
+
+
+def get_buddy(dbus_handle):
+ """DEPRECATED: Get a Buddy from a handle
+
+ THIS API WAS NOT THREAD SAFE! It has been removed to avoid
+ extremely hard-to-debug failures in activities. Use lookup_buddy
+ instead!
+
+ Code that read:
+
+ get_buddy( handle )
+ doSomething( handle, buddy )
+ doSomethingElse( buddy )
+
+ Translates to:
+
+ def withBuddy( buddy ):
+ doSomething( handle, buddy )
+ doSomethingElse( buddy )
+ lookup_buddy( handle, callback=withBuddy )
+ """
+ raise RuntimeError(
+ """get_buddy is not thread safe and will crash your activity (hard). Use lookup_buddy."""
+ )
+
+def _get_presence_service( ):
+ """Attempt to retrieve the presence service (check for offline condition)
+
+ The presence service, when offline, has no preferred connection type,
+ so we check that before returning the object...
+ """
+ log.debug( """About to import sugar.presence.presenceservice""" )
+ try:
+ log.debug( 'About to retrieve presence service instance' )
+ pservice = sugar.presence.presenceservice.get_instance()
+ try:
+ log.debug( ' Retrieved presence service instance: %s', pservice )
+ name, path = pservice.get_preferred_connection()
+ log.debug( ' Name = %s Path = %s', name, path )
+ except (TypeError,ValueError), err:
+ log.warn('Working in offline mode, cannot retrieve buddy information for %s: %s', handle, err )
+ raise OfflineError( """Unable to retrieve buddy information, currently offline""" )
+ else:
+ return pservice
+ except Exception, err:
+ log.error( """Failure in _get_presence_service: %s""", get_traceback( err ))
+
+def instance(idx=0):
+ return pygametubes[idx]
+
+
+class PygameTube(ExportedGObject):
+ '''The object whose instance is shared across D-bus
+
+ Call instance() to get the instance of this object for your activity service.
+ Its 'tube' property contains the underlying D-bus Connection.
+ '''
+ def __init__(self, tube, is_initiator, tube_id):
+ super(PygameTube, self).__init__(tube, DBUS_PATH)
+ log.info( 'PygameTube init' )
+ self.tube = tube
+ self.is_initiator = is_initiator
+ self.entered = False
+ self.ordered_bus_names = []
+ PEvent.post(PEvent.Event(CONNECT, id=tube_id))
+
+ if not self.is_initiator:
+ self.tube.add_signal_receiver(self.new_participant_cb, 'NewParticipants', DBUS_IFACE, path=DBUS_PATH)
+ self.tube.watch_participants(self.participant_change_cb)
+ self.tube.add_signal_receiver(self.broadcast_cb, 'Broadcast', DBUS_IFACE, path=DBUS_PATH, sender_keyword='sender')
+
+
+ def participant_change_cb(self, added, removed):
+ log.debug( 'participant_change_cb: %s %s', added, removed )
+ for handle, bus_name in added:
+ dbus_handle = self.tube.participants[handle]
+ self.ordered_bus_names.append(dbus_handle)
+ PEvent.post(PEvent.Event(PARTICIPANT_ADD, handle=dbus_handle))
+
+ for handle in removed:
+ dbus_handle = self.tube.participants[handle]
+ self.ordered_bus_names.remove(dbus_handle)
+ PEvent.post(PEvent.Event(PARTICIPANT_REMOVE, handle=dbus_handle))
+
+ if self.is_initiator:
+ if not self.entered:
+ # Initiator will broadcast a new ordered_bus_names each time
+ # a participant joins.
+ self.ordered_bus_names = [self.tube.get_unique_name()]
+ self.NewParticipants(self.ordered_bus_names)
+
+ self.entered = True
+
+ @signal(dbus_interface=DBUS_IFACE, signature='as')
+ def NewParticipants(self, ordered_bus_names):
+ '''This is the NewParticipants signal, sent when the authoritative list of ordered_bus_names changes.'''
+ log.debug("sending NewParticipants: %s" % ordered_bus_names)
+ pass
+
+ @signal(dbus_interface=DBUS_IFACE, signature='s')
+ def Broadcast(self, content):
+ '''This is the Broadcast signal; it sends a message to all other activity participants.'''
+ pass
+
+ @method(dbus_interface=DBUS_IFACE, in_signature='s', out_signature='', sender_keyword='sender')
+ def Tell(self, content, sender=None):
+ '''This is the targeted-message interface; called when a message is received that was sent directly to me.'''
+ PEvent.post(PEvent.Event(MESSAGE_UNI, handle=sender, content=content))
+
+ def broadcast_cb(self, content, sender=None):
+ '''This is the Broadcast callback, fired when someone sends a Broadcast signal along the bus.'''
+ PEvent.post(PEvent.Event(MESSAGE_MULTI, handle=sender, content=content))
+
+ def new_participant_cb(self, new_bus_names):
+ '''This is the NewParticipants callback, fired when someone joins or leaves.'''
+ log.debug("new participant. new bus names %s, old %s" % (new_bus_names, self.ordered_bus_names))
+ if self.ordered_bus_names != new_bus_names:
+ log.warn("ordered bus names out of sync with server, resyncing")
+ self.ordered_bus_names = new_bus_names
+
+def send_to(handle, content=""):
+ '''Sends the given message to the given buddy identified by handle.'''
+ log.debug( 'send_to: %s %s', handle, content )
+ #remote_proxy = dbus_get_object(handle, DBUS_PATH)
+ remote_proxy = get_object(handle, DBUS_PATH)
+ remote_proxy.Tell(content, reply_handler=dbus_msg, error_handler=dbus_err)
+
+def dbus_msg():
+ log.debug("async reply to send_to")
+def dbus_err(e):
+ log.error("async error: %s" % e)
+
+def broadcast(content=""):
+ '''Sends the given message to all participants.'''
+ log.debug( 'Broadcast: %s', content )
+ instance().Broadcast(content)
+
+def my_handle():
+ '''Returns the handle of this user
+
+ Note, you can get a DBusException from this if you have
+ not yet got a unique ID assigned by the bus. You may need
+ to delay calling until you are sure you are connected.
+ '''
+ log.debug( 'my handle' )
+ return instance().tube.get_unique_name()
+
+def is_initiator():
+ '''Returns the handle of this user.'''
+ log.debug( 'is initiator' )
+ return instance().is_initiator
+
+def get_participants():
+ '''Returns the list of active participants, in order of arrival.
+ List is maintained by the activity creator; if that person leaves it may not stay in sync.'''
+ log.debug( 'get_participants' )
+ try:
+ return instance().ordered_bus_names[:]
+ except IndexError, err:
+ return [] # no participants yet, as we don't yet have a connection
+
+def dbus_get_object(handle, path, warning=True):
+ '''Get a D-bus object from another participant
+
+ Note: this *must* be called *only* from the GTK mainloop, calling
+ it from Pygame will cause crashes! If you are *sure* you only ever
+ want to call methods on this proxy from GTK, you can use
+ warning=False to silence the warning log message.
+ '''
+ if warning:
+ log.warn( 'Use of dbus_get_object is only safe from the GTK mainloop, use dbus_get_object_proxy instead: %s %s', handle, path )
+ return instance().tube.get_object(handle, path)
+
+def get_object(handle, path):
+ '''Get a D-BUS proxy object from another participant for use in Pygame
+
+ This is how you can communicate with other participants using
+ arbitrary D-bus objects without having to manage the participants
+ yourself. You can use the returned proxy's methods from Pygame,
+ with your callbacks occuring in the Pygame thread, rather than
+ in the DBUS/GTK event loop.
+
+ Simply define a D-bus class with an interface and path that you
+ choose; when you want a reference to the corresponding remote
+ object on a participant, call this method.
+
+ returns an olpcgames.dbusproxy.DBUSProxy( ) object wrapping
+ the DBUSProxy object.
+
+ The dbus_get_object_proxy name is deprecated
+ '''
+ log.debug( 'DBUS get_object( %r %r )', handle, path )
+ from olpcgames import dbusproxy
+ return dbusproxy.DBUSProxy(
+ instance().tube.get_object( handle, path),
+ tube=instance().tube,
+ path=path
+ )
+
+dbus_get_object_proxy = get_object
diff --git a/olpcgames/pangofont.py b/olpcgames/pangofont.py
new file mode 100755
index 0000000..441dfd1
--- /dev/null
+++ b/olpcgames/pangofont.py
@@ -0,0 +1,346 @@
+"""Implement Pygame's font interface using Pango for international support
+
+Depends on:
+
+ pygtk (to get the pango context)
+ pycairo (for the pango rendering context)
+ python-pango (obviously)
+ numpy
+ (pygame)
+
+As soon as you import this module you have loaded *all* of the above.
+You can still use pygame.font until you decide to call install(), which
+will replace pygame.font with this module.
+
+Notes:
+
+ * no ability to load TTF files, PangoFont uses the font files registered
+ with GTK/X to render graphics, it cannot load an arbitrary TTF file.
+ Most non-Sugar Pygame games use bundled TTF files, which means
+ that you will likely need at least some changes to your font handling.
+
+ Note, however, that the Pygame Font class is available to load the TTF
+ files, so if you don't want to take advantage of PangoFont for already
+ written code, but want to use it for "system font" operations, you can
+ mix the two.
+
+ * metrics are missing, Pango can provide the information, but the more
+ involved metrics system means that translating to the simplified model
+ in Pygame has as of yet not been accomplished.
+
+ * better support for "exotic" languages and scripts (which is why we use it)
+
+The main problem with SDL_ttf is that it doesn't handle internationalization
+nearly as well as Pango (in fact, pretty much nothing does). However, it is
+fairly fast and it has a rich interface. You should avoid fonts where possible,
+prerender using Pango for internationalizable text, and use Pango or SDL_ttf
+for text that really needs to be rerendered each frame. (Use SDL_ttf if profiling
+demonstrates that performance is poor with Pango.)
+
+Note:
+ Font -- is the original Pygame Font class, which allows you to load
+ fonts from TTF files/filenames
+ PangoFont -- is the Pango-specific rendering engine which allows
+ for the more involved cross-lingual rendering operations.
+"""
+import pango
+import logging
+import pangocairo
+import pygame.rect, pygame.image
+import gtk
+import struct
+from pygame import surface
+from pygame.font import Font
+from olpcgames import _cairoimage
+
+log = logging.getLogger( 'olpcgames.pangofont' )
+##log.setLevel( logging.DEBUG )
+
+# Install myself on top of pygame.font
+def install():
+ """Replace Pygame's font module with this module"""
+ log.info( 'installing' )
+ from olpcgames import pangofont
+ import pygame
+ pygame.font = pangofont
+ import sys
+ sys.modules["pygame.font"] = pangofont
+
+class PangoFont(object):
+ """Base class for a pygame.font.Font-like object drawn by Pango
+
+ Attributes of note:
+
+ fd -- instances Pango FontDescription object
+ WEIGHT_* -- parameters for use with set_weight
+ STYLE_* -- parameters for use with set_style
+
+ """
+ WEIGHT_BOLD = pango.WEIGHT_BOLD
+ WEIGHT_HEAVY = pango.WEIGHT_HEAVY
+ WEIGHT_LIGHT = pango.WEIGHT_LIGHT
+ WEIGHT_NORMAL = pango.WEIGHT_NORMAL
+ WEIGHT_SEMIBOLD = pango.WEIGHT_SEMIBOLD
+ WEIGHT_ULTRABOLD = pango.WEIGHT_ULTRABOLD
+ WEIGHT_ULTRALIGHT = pango.WEIGHT_ULTRALIGHT
+ STYLE_NORMAL = pango.STYLE_NORMAL
+ STYLE_ITALIC = pango.STYLE_ITALIC
+ STYLE_OBLIQUE = pango.STYLE_OBLIQUE
+ def __init__(self, family=None, size=None, bold=False, italic=False, underline=False, fd=None):
+ """If you know what pango.FontDescription (fd) you want, pass it in as
+ 'fd'. Otherwise, specify any number of family, size, bold, or italic,
+ and we will try to match something up for you."""
+
+ # Always set the FontDescription (FIXME - only set it if the user wants
+ # to change something?)
+ if fd is None:
+ fd = pango.FontDescription()
+ if family is not None:
+ fd.set_family(family)
+ if size is not None:
+ log.debug( 'Pre-conversion size: %s', size )
+ size = int(size*1024)
+ log.debug( 'Font size: %s', size, )
+ fd.set_size(size) # XXX magic number, pango's scaling
+ self.fd = fd
+ self.set_bold( bold )
+ self.set_italic( italic )
+ self.set_underline( underline )
+
+ def render(self, text, antialias=True, color=(255,255,255), background=None ):
+ """Render the font onto a new Surface and return it.
+ We ignore 'antialias' and use system settings.
+
+ text -- (unicode) string with the text to render
+ antialias -- attempt to antialias the text or not
+ color -- three or four-tuple of 0-255 values specifying rendering
+ colour for the text
+ background -- three or four-tuple of 0-255 values specifying rendering
+ colour for the background, or None for trasparent background
+
+ returns a pygame image instance
+ """
+ log.info( 'render: %r, antialias = %s, color=%s, background=%s', text, antialias, color, background )
+
+ layout = self._createLayout( text )
+ # determine pixel size
+ (logical, ink) = layout.get_pixel_extents()
+ ink = pygame.rect.Rect(ink)
+
+ # Create a new Cairo ImageSurface
+ csrf,cctx = _cairoimage.newContext( ink.w, ink.h )
+ cctx = pangocairo.CairoContext(cctx)
+
+ # Mangle the colors on little-endian machines. The reason for this
+ # is that Cairo writes native-endian 32-bit ARGB values whereas
+ # Pygame expects endian-independent values in whatever format. So we
+ # tell our users not to expect transparency here (avoiding the A issue)
+ # and we swizzle all the colors around.
+
+ # render onto it
+ if background is not None:
+ background = _cairoimage.mangle_color( background )
+ cctx.set_source_rgba(*background)
+ cctx.paint()
+
+ log.debug( 'incoming color: %s', color )
+ color = _cairoimage.mangle_color( color )
+ log.debug( ' translated color: %s', color )
+
+ cctx.new_path()
+ cctx.layout_path(layout)
+ cctx.set_source_rgba(*color)
+ cctx.fill()
+
+ # Create and return a new Pygame Image derived from the Cairo Surface
+ return _cairoimage.asImage( csrf )
+
+ def set_bold( self, bold=True):
+ """Set our font description's weight to "bold" or "normal"
+
+ bold -- boolean, whether to set the value to "bold" weight or not
+ """
+ if bold:
+ self.set_weight( self.WEIGHT_BOLD )
+ else:
+ self.set_weight( self.WEIGHT_NORMAL )
+ def set_weight( self, weight ):
+ """Explicitly set our pango-style weight value"""
+ self.fd.set_weight( weight )
+ return self.get_weight()
+ def get_weight( self ):
+ """Explicitly get our pango-style weight value"""
+ return self.fd.get_weight()
+ def get_bold( self ):
+ """Return whether our font's weight is bold (or above)"""
+ return self.fd.get_weight() >= pango.WEIGHT_BOLD
+
+ def set_italic( self, italic=True ):
+ """Set our "italic" value (style)"""
+ if italic:
+ self.set_style( self.STYLE_ITALIC )
+ else:
+ self.set_style( self.STYLE_NORMAL )
+ def set_style( self, style ):
+ """Set our font description's pango-style"""
+ self.fd.set_style( style )
+ return self.fd.get_style()
+ def get_style( self ):
+ """Get our font description's pango-style"""
+ return self.fd.get_style()
+ def get_italic( self ):
+ """Return whether we are currently italicised"""
+ return self.fd.get_style() == self.STYLE_ITALIC # what about oblique?
+
+ def set_underline( self, underline=True ):
+ """Set our current underlining properly"""
+ self.underline = underline
+ def get_underline( self ):
+ """Retrieve our current underline setting"""
+ return self.underline
+
+ def _createLayout( self, text ):
+ """Produces a Pango layout describing this text in this font"""
+ # create layout
+ layout = pango.Layout(gtk.gdk.pango_context_get())
+ layout.set_font_description(self.fd)
+ if self.underline:
+ attrs = layout.get_attributes()
+ if not attrs:
+ attrs = pango.AttrList()
+ attrs.insert(pango.AttrUnderline(pango.UNDERLINE_SINGLE, 0, 32767))
+ layout.set_attributes( attrs )
+ layout.set_text(text)
+ return layout
+
+ def size( self, text ):
+ """Determine space required to render given text
+
+ returns tuple of (width,height)
+ """
+ layout = self._createLayout( text )
+ (logical, ink) = layout.get_pixel_extents()
+ ink = pygame.rect.Rect(ink)
+ return (ink.width,ink.height)
+
+## def get_linesize( self ):
+## """Determine inter-line spacing for the font"""
+## font = self.get_context().load_font( self.fd )
+## metrics = font.get_metrics()
+## return pango.PIXELS( metrics.get_ascent() )
+## def get_height( self ):
+## def get_ascent( self ):
+## def get_descent( self ):
+
+
+class SysFont(PangoFont):
+ """Construct a PangoFont from a font description (name), size in pixels,
+ bold, and italic designation. Similar to SysFont from Pygame."""
+ def __init__(self, name, size, bold=False, italic=False):
+ fd = pango.FontDescription(name)
+ fd.set_absolute_size(size*pango.SCALE)
+ if bold:
+ fd.set_weight(pango.WEIGHT_BOLD)
+ if italic:
+ fd.set_style(pango.STYLE_OBLIQUE)
+ super(SysFont, self).__init__(fd=fd)
+
+# originally defined a new class, no reason for that...
+NotImplemented = NotImplementedError
+
+def match_font(name,bold=False,italic=False):
+ """Stub, does not work, use fontByDesc instead"""
+ raise NotImplementedError("PangoFont doesn't support match_font directly, use SysFont or .fontByDesc")
+
+def fontByDesc(desc="",bold=False,italic=False):
+ """Constructs a FontDescription from the given string representation.
+
+The format of the fontByDesc string representation is passed directly
+to the pango.FontDescription constructor and documented at:
+
+ http://www.pygtk.org/docs/pygtk/class-pangofontdescription.html#constructor-pangofontdescription
+
+Bold and italic are provided as a convenience.
+
+The format of the string representation is:
+
+ "[FAMILY-LIST] [STYLE-OPTIONS] [SIZE]"
+
+where FAMILY-LIST is a comma separated list of families optionally terminated by a comma, STYLE_OPTIONS is a whitespace separated list of words where each WORD describes one of style, variant, weight, or stretch, and SIZE is an decimal number (size in points). For example the following are all valid string representations:
+
+ "sans bold 12"
+ "serif,monospace bold italic condensed 16"
+ "normal 10"
+
+The commonly available font families are: Normal, Sans, Serif and Monospace. The available styles are:
+Normal the font is upright.
+Oblique the font is slanted, but in a roman style.
+Italic the font is slanted in an italic style.
+
+The available weights are:
+Ultra-Light the ultralight weight (= 200)
+Light the light weight (=300)
+Normal the default weight (= 400)
+Bold the bold weight (= 700)
+Ultra-Bold the ultra-bold weight (= 800)
+Heavy the heavy weight (= 900)
+
+The available variants are:
+Normal
+Small-Caps
+
+The available stretch styles are:
+Ultra-Condensed the smallest width
+Extra-Condensed
+Condensed
+Semi-Condensed
+Normal the normal width
+Semi-Expanded
+Expanded
+Extra-Expanded
+Ultra-Expanded the widest width
+ """
+ fd = pango.FontDescription(name)
+ if bold:
+ fd.set_weight(pango.WEIGHT_BOLD)
+ if italic:
+ fd.set_style(pango.STYLE_OBLIQUE)
+ return PangoFont(fd=fd)
+
+def get_init():
+ """Return boolean indicating whether we are initialised
+
+ Always returns True
+ """
+ return True
+
+def init():
+ """Initialise the module (null operation)"""
+ pass
+
+def quit():
+ """De-initialise the module (null operation)"""
+ pass
+
+def get_default_font():
+ """Return default-font specification to be passed to e.g. fontByDesc"""
+ return "sans"
+
+def get_fonts():
+ """Return the set of all fonts available (currently just 3 generic types)"""
+ return ["sans","serif","monospace"]
+
+
+def stdcolor(color):
+ """Produce a 4-element 0.0-1.0 color value from input"""
+ def fixlen(color):
+ if len(color) == 3:
+ return tuple(color) + (255,)
+ elif len(color) == 4:
+ return color
+ else:
+ raise TypeError("What sort of color is this: %s" % (color,))
+ return [_fixColorBase(x) for x in fixlen(color)]
+def _fixColorBase( v ):
+ """Return a properly clamped colour in floating-point space"""
+ return max((0,min((v,255.0))))/255.0
diff --git a/olpcgames/pausescreen.py b/olpcgames/pausescreen.py
new file mode 100755
index 0000000..113a0ea
--- /dev/null
+++ b/olpcgames/pausescreen.py
@@ -0,0 +1,116 @@
+"""Display a "paused" version of the currently-displayed screen
+
+This code is largely cribbed from the Pippy activity's display code,
+but we try to be a little more generally usable than they are, as
+we have more involved activities using the code.
+
+We use svgsprite to render a graphic which is stored in the
+olpcgames data directory over a dimmed version of the current
+screen contents.
+
+_LAST_EVENT_TIME -- tracks the last time that we saw an event
+ come across the wire.
+"""
+import logging
+log = logging.getLogger( 'olpcgames.pausescreen' )
+import pygame
+from pygame import sprite
+
+_LAST_EVENT_TIME = 0
+
+def _set_last_event_time( time=None ):
+ """Set time as the last event time
+
+ time -- if None, pygame.time.get_ticks() is used
+
+ returns time set
+ """
+ global _LAST_EVENT_TIME
+ if time is None:
+ time = pygame.time.get_ticks()
+ _LAST_EVENT_TIME = time
+ return time
+
+def last_event_time( ):
+ """Return the duration since last event for pausing operations
+
+ returns time in seconds
+ """
+ global _LAST_EVENT_TIME
+ return (pygame.time.get_ticks() - _LAST_EVENT_TIME)/1000.
+
+
+def get_events( sleep_timeout = 10, pause=None, **args ):
+ """Retrieve the set of pending events or sleep
+
+ sleep_timeout -- dormant period before we invoke pause_screen
+ pause -- callable to produce visual notification of pausing, normally
+ by taking the current screen and modifying it in some way. Defaults
+ to pauseScreen in this module. If you return nothing from this
+ function then no restoration or display-flipping will occur
+ *args -- if present, passed to 'pause' to configuration operation (e.g.
+ to specify a different overlaySVG file)
+
+ returns set of pending events (potentially empty)
+ """
+ if not pause:
+ pause = pauseScreen
+ events = pygame.event.get( )
+ if not events:
+ log.info( 'No events in queue' )
+ old_screen = None
+ if last_event_time() > sleep_timeout:
+ # we've been waiting long enough, go to sleep visually
+ log.warn( 'Pausing activity after %s with function %s', sleep_timeout, pause )
+ old_screen = pause( )
+ if old_screen:
+ pygame.display.flip()
+ # now we wait until there *are* some events (efficiently)
+ # and retrieve any extra events that are waiting...
+ events = [ pygame.event.wait() ] + pygame.event.get()
+ log.warn( 'Activity restarted')
+ if old_screen:
+ restoreScreen( old_screen )
+ if events:
+ _set_last_event_time()
+ return events
+
+def pauseScreen( overlaySVG=None ):
+ """Display a "Paused" screen and suspend
+
+ This default implementation will not do anything to shut down your
+ simulation or other code running in other threads. It will merely block
+ this thread (the pygame thread) until an event shows up in the
+ eventwrap queue.
+
+ Returns a surface to pass to restoreScreen to continue...
+ """
+ from olpcgames import svgsprite
+ if not overlaySVG:
+ from olpcgames.data import sleeping_svg
+ overlaySVG = sleeping_svg.data
+ screen = pygame.display.get_surface()
+ old_screen = screen.copy() # save this for later.
+ pause_sprite = svgsprite.SVGSprite(
+ overlaySVG,
+ )
+ pause_sprite.rect.center = screen.get_rect().center
+ group = sprite.RenderUpdates( )
+ group.add( pause_sprite )
+
+ # dim the screen and display the 'paused' message in the center.
+ BLACK = (0,0,0)
+ WHITE = (255,255,255)
+ dimmed = screen.copy()
+ dimmed.set_alpha(128)
+ screen.fill(BLACK)
+ screen.blit(dimmed, (0,0))
+
+ group.draw( screen )
+ return old_screen
+
+def restoreScreen( old_screen ):
+ """Restore the original screen and return"""
+ screen = pygame.display.get_surface()
+ screen.blit(old_screen, (0,0))
+ return old_screen
diff --git a/olpcgames/svgsprite.py b/olpcgames/svgsprite.py
new file mode 100755
index 0000000..ad247dd
--- /dev/null
+++ b/olpcgames/svgsprite.py
@@ -0,0 +1,84 @@
+"""RSVG/Cairo-based rendering of SVG into Pygame Images"""
+from pygame import sprite, Rect
+from olpcgames import _cairoimage
+
+class SVGSprite( sprite.Sprite ):
+ """Sprite class which renders SVG source-code as a Pygame image
+
+ Note:
+
+ Currently this sprite class is a bit over-engineered, it gets in the way
+ if you want to, e.g. animate among a number of SVG drawings, as it
+ assumes that setSVG will always set a single SVG file for rendering.
+ """
+ rect = image = None
+ resolution = None
+ def __init__(
+ self, svg=None, size=None, *args
+ ):
+ """Initialise the svg sprite
+
+ svg -- svg source text (i.e. content of an svg file)
+ size -- optional, to constrain size, (width,height), leaving one
+ as None or 0 causes proportional scaling, leaving both
+ as None or 0 causes natural scaling (screen resolution)
+ args -- if present, groups to which to automatically add
+ """
+ self.size = size
+ super( SVGSprite, self ).__init__( *args )
+ if svg:
+ self.setSVG( svg )
+ def setSVG( self, svg ):
+ """Set our SVG source"""
+ self.svg = svg
+ # XXX could delay this until actually asked to display...
+ if self.size:
+ width,height = self.size
+ else:
+ width,height = None,None
+ self.image = self._render( width,height ).convert_alpha()
+ rect = self.image.get_rect()
+ if self.rect:
+ rect.move( self.rect ) # should let something higher-level do that...
+ self.rect = rect
+
+ def _render( self, width, height ):
+ """Render our SVG to a Pygame image"""
+ import rsvg
+ handle = rsvg.Handle( data = self.svg )
+ originalSize = (width,height)
+ scale = 1.0
+ hw,hh = handle.get_dimension_data()[:2]
+ if hw and hh:
+ if not width:
+ if not height:
+ width,height = hw,hh
+ else:
+ scale = float(height)/hh
+ width = hh/float(hw) * height
+ elif not height:
+ scale = float(width)/hw
+ height = hw/float(hh) * width
+ else:
+ # scale only, only rendering as large as it is...
+ if width/height > hw/hh:
+ # want it taller than it is...
+ width = hh/float(hw) * height
+ else:
+ height = hw/float(hh) * width
+ scale = float(height)/hh
+
+ csrf, ctx = _cairoimage.newContext( int(width), int(height) )
+ ctx.scale( scale, scale )
+ handle.render_cairo( ctx )
+ return _cairoimage.asImage( csrf )
+ return None
+ def copy( self ):
+ """Create a copy of this sprite without reloading the svg image"""
+ result = self.__class__(
+ size = self.size
+ )
+ result.image = self.image
+ result.rect = Rect(self.rect)
+ result.resolution = self.resolution
+ return result
diff --git a/olpcgames/textsprite.py b/olpcgames/textsprite.py
new file mode 100755
index 0000000..7663630
--- /dev/null
+++ b/olpcgames/textsprite.py
@@ -0,0 +1,40 @@
+"""Simple Sprite sub-class that renders via a PangoFont"""
+from pygame import sprite
+from olpcgames import pangofont
+
+class TextSprite( sprite.Sprite ):
+ """Sprite with a simple text renderer"""
+ image = rect = text = color = background = None
+ def __init__( self, text=None, family=None, size=None, bold=False, italic=False, color=None, background=None ):
+ super( TextSprite, self ).__init__( )
+ self.font = pangofont.PangoFont( family=family, size=size, bold=bold, italic=italic )
+ self.set_color( color )
+ self.set_background( background )
+ self.set_text( text )
+ def set_text( self, text ):
+ """Set our text string and render to a graphic"""
+ self.text = text
+ self.render( )
+ def set_color( self, color =None):
+ """Set our rendering colour (default white)"""
+ self.color = color or (255,255,255)
+ self.render()
+ def set_background( self, color=None ):
+ """Set our background color, default transparent"""
+ self.background = color
+ self.render()
+ def render( self ):
+ """Render our image and rect (or None,None)
+
+ After a render you will need to move the rect member to the
+ correct location on the screen.
+ """
+ if self.text:
+ self.image = self.font.render( self.text, color = self.color, background = self.background )
+ currentRect = self.rect
+ self.rect = self.image.get_rect()
+ if currentRect:
+ self.rect.center = currentRect.center
+ else:
+ self.rect = None
+ self.image = None
diff --git a/olpcgames/util.py b/olpcgames/util.py
new file mode 100755
index 0000000..49a23b0
--- /dev/null
+++ b/olpcgames/util.py
@@ -0,0 +1,79 @@
+"""Abstraction layer for working outside the Sugar environment"""
+import traceback, cStringIO
+import logging
+log = logging.getLogger( 'olpcgames.util' )
+import os
+import os.path
+
+NON_SUGAR_ROOT = '~/.sugar/default/olpcgames'
+
+try:
+ from sugar.activity.activity import get_bundle_path as _get_bundle_path
+ def get_bundle_path( ):
+ """Retrieve bundle path from activity with fix for silly registration bug"""
+ path = _get_bundle_path()
+ if path.endswith( '.activity.activity' ):
+ log.warn( '''Found double .activity suffix in bundle path, truncating: %s''', path )
+ path = path[:-9]
+ return path
+except ImportError:
+ log.warn( '''Do not appear to be running under Sugar, stubbing-in get_bundle_path''' )
+ def get_bundle_path():
+ """Retrieve a substitute data-path for non OLPC systems"""
+ return os.getcwd()
+
+
+def get_activity_root( ):
+ """Return the activity root for data storage operations
+
+ If the activity is present, returns the activity's root,
+ otherwise returns NON_SUGAR_ROOT as the directory.
+ """
+ import olpcgames
+ if olpcgames.ACTIVITY:
+ return olpcgames.ACTIVITY.get_activity_root()
+ else:
+ return os.path.expanduser( NON_SUGAR_ROOT )
+
+def data_path(file_name):
+ """Return the full path to a file in the data sub-directory of the bundle"""
+ return os.path.join(get_bundle_path(), 'data', file_name)
+def tmp_path(file_name):
+ """Return the full path to a file in the temporary directory"""
+ return os.path.join(get_activity_root(), 'tmp', file_name)
+
+def get_traceback(error):
+ """Get formatted traceback from current exception
+
+ error -- Exception instance raised
+
+ Attempts to produce a 10-level traceback as a string
+ that you can log off. Use like so:
+
+ try:
+ doSomething()
+ except Exception, err:
+ log.error(
+ '''Failure during doSomething with X,Y,Z parameters: %s''',
+ util.get_traceback( err ),
+ )
+ """
+ if error is None:
+ error = []
+ for (f,l,func,statement) in traceback.extract_stack()[:-2]:
+ if statement:
+ statement = ': %s'%( statement, )
+ if func:
+ error.append( '%s.%s (%s)%s'%( f,func,l, statement))
+ else:
+ error.append( '%s (%s)%s'%( f,l, statement))
+ return "\n".join( error )
+ else:
+ exception = str(error)
+ file = cStringIO.StringIO()
+ try:
+ traceback.print_exc( limit=10, file = file )
+ exception = file.getvalue()
+ finally:
+ file.close()
+ return exception
diff --git a/olpcgames/video.py b/olpcgames/video.py
new file mode 100755
index 0000000..032aa13
--- /dev/null
+++ b/olpcgames/video.py
@@ -0,0 +1,178 @@
+"""Video widget for displaying a gstreamer pipe
+
+Note: currently this module is not all that elegant or useful,
+we need a better recipe for using and working with Video
+under OLPCGames.
+"""
+import logging
+log = logging.getLogger( 'olpcgames.video' )
+#log.setLevel( logging.INFO )
+import os
+import signal
+import pygame
+import weakref
+import olpcgames
+from olpcgames import _gtkmain
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gst
+
+class VideoWidget(gtk.DrawingArea):
+ """Widget to render GStreamer video over our Pygame Canvas
+
+ The VideoWidget is a simple GTK window which is
+ held by the PygameCanvas, just as is the Pygame
+ window we normally use. As such this approach
+ *cannot* work without the GTK wrapper.
+
+ It *should* be possible to use raw X11 operations
+ to create a child window of the Pygame/SDL window
+ and use that for the same purpose, but that would
+ require some pretty low-level ctypes hacking.
+
+ Attributes of Note:
+
+ rect -- Pygame rectangle which tells us where to
+ display ourselves, setting the rect changes the
+ position and size of the window.
+ """
+ _imagesink = None
+ _renderedRect = None
+ def __init__(self, rect=None, force_aspect_ratio=True):
+ super(VideoWidget, self).__init__()
+ self.unset_flags(gtk.DOUBLE_BUFFERED)
+ if rect is None:
+ rect = pygame.Rect( (0,0), (160,120))
+ self.rect = rect
+ self.force_aspect_ratio = force_aspect_ratio
+ self.set_size_request(rect.width,rect.height)
+ olpcgames.WIDGET.put( self, rect.left,rect.top)
+ self._renderedRect = rect
+ self.show()
+
+ def set_rect( self, rect ):
+ """Set our rectangle (area of the screen)"""
+ log.debug( 'Set rectangle: %s', rect )
+ self.set_size_request(rect.width,rect.height)
+ olpcgames.WIDGET.move( self, rect.left,rect.top)
+ self.rect = rect
+
+ def do_expose_event(self, event):
+ """Handle exposure event (trigger redraw by gst)"""
+ if self._imagesink:
+ self._imagesink.expose()
+ return False
+ else:
+ return True
+
+ def set_sink(self, sink):
+ """Set our window-sink for output"""
+ assert self.window.xid
+ self._imagesink = sink
+ self._imagesink.set_xwindow_id(self.window.xid)
+ self._imagesink.set_property('force-aspect-ratio', self.force_aspect_ratio)
+
+class PygameWidget( object ):
+ """Render "full-screen" video to the entire Pygame screen
+
+ Not particularly useful unless this happens to be exactly what you need.
+ """
+ def __init__( self ):
+ try:
+ window_id = pygame.display.get_wm_info()['window']
+ except KeyError, err: # pygame-ctypes...
+ window_id = int(os.environ['SDL_WINDOWID'])
+ self.window_id = window_id
+ self._imagesink = None
+ #self._holder = _gtkmain.Holder()
+ def set_sink( self, sink ):
+ """Set up our gst sink"""
+ log.info( 'Setting sink: %s', sink )
+ self._imagesink = sink
+ sink.set_xwindow_id( self.window_id )
+
+#pipe_desc = 'v4l2src ! video/x-raw-yuv,width=160,height=120 ! ffmpegcolorspace ! xvimagesink'
+class Player(object):
+ pipe_desc = 'v4l2src ! ffmpegcolorspace ! video/x-raw-yuv ! xvimagesink'
+ test_pipe_desc = 'videotestsrc ! ffmpegcolorspace ! video/x-raw-yuv ! xvimagesink'
+ _synchronized = False
+ def __init__(self, videowidget, pipe_desc=pipe_desc):
+ self._playing = False
+ self._videowidget = videowidget
+
+ self._pipeline = gst.parse_launch(pipe_desc)
+
+ bus = self._pipeline.get_bus()
+ bus.enable_sync_message_emission()
+ bus.add_signal_watch()
+ bus.connect('sync-message::element', self.on_sync_message)
+ bus.connect('message', self.on_message)
+
+ def play(self):
+ log.info( 'Play' )
+ if self._playing == False:
+ self._pipeline.set_state(gst.STATE_PLAYING)
+ self._playing = True
+
+ def pause(self):
+ log.info( 'Pause' )
+ if self._playing == True:
+ if self._synchronized:
+ log.debug( ' pause already sync\'d' )
+ self._pipeline.set_state(gst.STATE_PAUSED)
+ self._playing = False
+ def stop( self ):
+ """Stop all playback"""
+ self._pipeline.set_state( gst.STATE_NULL )
+
+ def on_sync_message(self, bus, message):
+ log.info( 'Sync: %s', message )
+ if message.structure is None:
+ return
+ if message.structure.get_name() == 'prepare-xwindow-id':
+ self._synchronized = True
+ self._videowidget.set_sink(message.src)
+
+ def on_message(self, bus, message):
+ log.info( 'Message: %s', message )
+ t = message.type
+ if t == gst.MESSAGE_ERROR:
+ err, debug = message.parse_error()
+ log.warn("Video error: (%s) %s" ,err, debug)
+ self._playing = False
+
+if __name__ == "__main__":
+ # Simple testing code...
+ logging.basicConfig()
+ log.setLevel( logging.DEBUG )
+ from pygame import image,display, event
+ import pygame
+ def main():
+ display.init()
+ maxX,maxY = display.list_modes()[0]
+ screen = display.set_mode( (maxX/3, maxY/3 ) )
+
+ display.flip()
+
+ pgw = PygameWidget( )
+ p = Player( pgw, pipe_desc=Player.test_pipe_desc )
+ p.play()
+
+ clock = pygame.time.Clock()
+
+ running = True
+ while running:
+ clock.tick( 60 )
+ for evt in [pygame.event.wait()] + pygame.event.get():
+ if evt.type == pygame.KEYDOWN:
+ if p._playing:
+ p.pause()
+ else:
+ p.play()
+ elif evt.type == pygame.QUIT:
+ p.stop()
+ running = False
+ #display.flip()
+ main()
diff --git a/po/POTFILES.in b/po/POTFILES.in
new file mode 100644
index 0000000..79fe2d7
--- /dev/null
+++ b/po/POTFILES.in
@@ -0,0 +1,5 @@
+activity.py
+InstrumentPanel.py
+J2JToolbar.py
+run.py
+setup.py \ No newline at end of file
diff --git a/run.py b/run.py
new file mode 100644
index 0000000..dd0a105
--- /dev/null
+++ b/run.py
@@ -0,0 +1,553 @@
+#! /usr/bin/env python
+
+import subprocess
+import logging, olpcgames
+import olpcgames.pausescreen as pausescreen
+import olpcgames.mesh as mesh
+from olpcgames import camera
+from sugar.presence import presenceservice
+from threading import Timer
+from math import ceil, sqrt
+
+
+from City.CsHelpers import *
+from City.Parameters import Instrument
+import City.City as City
+
+log = logging.getLogger( 'City run' )
+log.setLevel( logging.DEBUG )
+
+def buildInstruments(names, imgpath, screensize, scale):
+ "returns a list of Instrument objects, loaded with images"
+ Instruments = [Instrument(names[i]) for i in range(len(names))]
+ imagefiles = ResourceList(imgpath, '.png')
+ startx = 0
+ for i in Instruments:
+ for f in imagefiles:
+ if i.name.startswith(f[:4]):
+ i.loadImage(ImagePath+'/'+f, scale)
+ i.x = screensize[0] * 0.8 - startx
+ i.y = (screensize[1] - i.image.get_size()[1]) * 0.5
+ i.Touch = True
+ startx = startx + (screensize[0] * 0.8) / len(Instruments)
+ return Instruments
+
+def getInstrumentParameters(scene, inm):
+ "return a list of parameters values for the instrument, in order of PNAMES"
+ pobj = scene.Params
+ result = []
+ for pnm in PNAMES:
+ result.append(pobj.getValue(pnm, inm))
+ return result
+
+def setInstrumentParameters(scene, inm, vlst):
+ "sets parameters for an instrument"
+ pobj = scene.Params
+ for pnm,val in zip(PNAMES, vlst):
+ pobj.setValue(pnm, inm, val)
+ return True
+
+KEYCODES = {276:"Nudge|Left", 275:"Nudge|Right", 274:"Nudge|Down", 273:"Nudge|Up",
+ 260:"Nudge|Left", 262:"Nudge|Right", 258:"Nudge|Down", 264:"Nudge|Up",
+ 263: "Instrument|Bass", 257:"Instrument|Chords", 265:"Instrument|Lead", 259:"Instrument|Drums",
+ 49: "Instrument|Bass", 50:"Instrument|Chords", 51:"Instrument|Lead", 52:"Instrument|Drums",
+ 112: "Parameter|Pitch", 118:"Parameter|Volume", 100:"Parameter|Density", 108:"Parameter|Length", 116:"Parameter|Timbre",
+ 304: "Modifier|Shift"}
+
+
+class jamScene( object ):
+ def __init__(self, screen, scene = 'City', key = 'A', mode = 'minor', tempo = 120, initial_parameters = {}):
+ self.scene = City.ScenePlayer(scene, key, mode, tempo, initial_parameters)
+ self.music_player = City.makePlayer(self.scene)
+ self.beatEstimator = beatEstimator(self.music_player.tempoMult, 0.17, self.music_player.beatlimit)
+ self.pending_instrument_assignment = []
+ self.latency_counter = 0
+ self.latency = [0.07]
+ self.latency_time_ID = {}
+ self._syncloop_running = 0
+ global schedEvent, now
+ schedEvent = self.scene.TimeQueue.schedEvent
+ now = self.scene.cs.perfTime
+ self.screen = screen
+ screenRect = screen.get_rect()
+ self.screenSize = screen.get_size()
+ self.playArea = pygame.Rect(screenRect.left,screenRect.top, screenRect.width, screenRect.height * 0.8)
+ if olpcgames.ACTIVITY:
+ olpcgames.ACTIVITY.playArea = self.playArea
+ olpcgames.ACTIVITY.jamScene = self
+ self.panelArea = pygame.Rect(screenRect.left,screenRect.height * 0.8, screenRect.width, screenRect.height * 0.2)
+ self.TemplateInstruments = buildInstruments(INAMES, ImagePath, self.playArea.size, 2)
+ for oni in self.TemplateInstruments:
+ oni.activate()
+ self.PanelInstruments = buildInstruments(INAMES, ImagePath, self.panelArea.size, 1.5)
+ for pnl in self.PanelInstruments:
+ pnl.Touch = True
+ pnl.activate()
+ imagesize = self.TemplateInstruments[0].image.get_size()
+ self.panelSize = (self.screenSize[0], imagesize[1] + 10)
+ #movement limits
+ self.xmin = self.playArea.left + imagesize[0] * 0.5
+ self.xmax = self.playArea.right - imagesize[0] * 0.5
+ self.ymax = self.playArea.bottom - imagesize[1] * 0.5
+ self.ymin = self.playArea.top + imagesize[1] * 0.5
+ #interface key codes
+ self.keycode = KEYCODES
+ #various states
+ self.keyActions = []
+ self.selectedInstrument = self.TemplateInstruments[0]
+ self.occupiedInstruments = {self.selectedInstrument.name: None}
+ self.myself = None
+ self.sharer = False
+ self.connected = False
+ self.timeTally = []
+ self.running = True
+ self.Vparam = "Pitch"
+ self.Hparam = "Density"
+ #interface controls
+ self.movingInstrument = False
+ #initial draw
+ panelColour = (0,0,0)
+ self.snap_store = (olpcgames.ACTIVITY.snap_store if platform == 'Sugar' else [])
+ self.feedbackgroundImage = None
+ self.setbackgroundImage(pygame.image.load(ImagePath + "/jam2jamXO_2.png").convert())
+ self.panel = pygame.Surface((self.panelArea.width, self.panelArea.height))
+ self.panel.fill(panelColour)
+ self.screen.blit(self.panel, self.panelArea)
+ pygame.display.flip()
+ for pnl in self.PanelInstruments: pnl.y = pnl.y() + self.playArea.height
+ def setbackgroundImage(self, img):
+ self.backgroundImage = img
+ self.screen.blit(self.backgroundImage, (0,0), self.playArea)
+ self.selectedInstrument.Touch = True
+ def updatePanel(self):
+ "redraw panel icons"
+ for pi in self.PanelInstruments:
+ if not pi.Touch:
+ pass
+ else:
+ if pi.name in self.occupiedInstruments:
+ pi.deactivate()
+ else:
+ pi.activate()
+ self.screen.blit(pi.image, pi.Rect)
+ pi.Touch = False
+ def runloop(self):
+ "main game loop"
+ clock = pygame.time.Clock()
+ imgcnt = 0
+ self.music_player.playLoop(now())
+ while self.running:
+ events = (pausescreen.get_events(sleep_timeout = 43200) if platform == 'Sugar' else pygame.event.get())
+ for event in events:
+ self.eventAction(event)
+ for act in self.keyActions:
+ self.interfaceAction(act)
+ if self.feedbackgroundImage:
+ self.setbackgroundImage(self.feedbackgroundImage)
+ self.feedbackgroundImage = None
+ self.updateInspos()
+ self.updatePanel()
+ if platform == 'Sugar': currentcnt = len(self.snap_store)
+ else: currentcnt = 0
+ if imgcnt == currentcnt:
+ pass
+ else:
+ self.music_player.picture_cycle = [self, True]
+ imgcnt = currentcnt
+ pygame.display.flip()
+ clock.tick(25)
+ def updateInspos(self):
+ "animate selected instrument."
+ ins = self.selectedInstrument
+ if ins.Touch:
+ xval = self.scene.Params.getValue(self.Hparam, ins.name)
+ yval = self.scene.Params.getValue(self.Vparam, ins.name)
+ xpos = rescale(xval, 0,1,self.xmin, self.xmax)
+ ypos = rescale(yval, 0,1,self.ymax, self.ymin)
+ self.screen.blit(self.backgroundImage, ins.Rect, ins.Rect)
+ ins.ctr = (xpos, ypos)
+ ins.Touch = False
+ self.screen.blit(ins.image, ins.Rect)
+ def sendSync(self):
+ "Tell audio loop to broadcast time and beat messages"
+ if self._syncloop_running:
+ self.music_player.sendSync = True
+ log.info("sent sync")
+ schedEvent(now() + 10.7, self.sendSync)
+ def setselectedInstrument(self, ins):
+ "select the instrument onscreen"
+ self.selectedInstrument = ins
+ if ins.name not in self.occupiedInstruments:
+ self.occupiedInstruments.update({ins.name:str(self.myself)})
+ self.selectedInstrument.Touch = True
+ def eventAction(self, event):
+ "detect events, and select action"
+ if event.type == pygame.QUIT:
+ self.music_player.freeze()
+ self.running = False
+ elif event.type == pygame.USEREVENT:
+ if hasattr(event, "action"):
+ if event.action.startswith("Parameter"):
+ args = event.action.split('|')
+ if args[1] == "Horizontal":
+ self.Hparam = args[2]
+ self.selectedInstrument.Touch = True
+ elif args[1] == "Vertical":
+ self.Vparam = args[2]
+ self.selectedInstrument.Touch = True
+ else:
+ raise ValueError, 'Unknown Parameter Action %s' %args
+ elif event.action.startswith('Reload'):
+ #should look always like this: "Reload|name|key:mode|tempo|defaults"
+ args = event.action.split('|')
+ name = args[1]
+ key = ('E' if args[2] == 'None' else args[2])
+ mode = ('minor' if args[3] == 'None' else args[3])
+ tempo = (117 if args[4] == 'None' else int(args[4]))
+ d = eval(args[5])
+ defaults = (d if d else {})
+ self.load_scene(name, key, mode, tempo, defaults) #this call blocks
+ if self.pending_instrument_assignment: #now check if we are waiting to assign instruments and params.
+ self.receiveMessage("AuthorisedInstrument|%s|%s" %(self.pending_instrument_assignment[0], self.pending_instrument_assignment[1]), self.myself)
+ elif event.action.startswith("Shared"):
+ self.sharer = "Pending"
+ log.info("Sharing activity")
+ elif event.action.startswith("Joined"):
+ log.info("Joined Activity")
+ else:
+ log.debug("unknown parameter change: %s", event.action)
+ else: log.debug("ignoring USEREVENT %s", event)
+ elif event.type == pygame.MOUSEBUTTONDOWN:
+ x,y = event.pos
+ Ins = self.selectedInstrument
+ if Ins.Rect.collidepoint(x,y):
+ self.movingInstrument = Ins
+ else:
+ for Panndx in range(len(self.PanelInstruments)):
+ Pan = self.PanelInstruments[Panndx]
+ if Pan.Rect.collidepoint(x,y):
+ if Pan.active: self.requestInstrument(Pan.name)
+ break
+ elif event.type == pygame.MOUSEMOTION:
+ if self.movingInstrument:
+ insname = self.movingInstrument.name
+ self.scene.Params.setValue(self.Hparam, insname, rescale(event.pos[0], self.playArea.left, self.playArea.right, 0, 1))
+ self.scene.Params.setValue(self.Vparam, insname, limit(rescale(event.pos[1], self.playArea.bottom, self.playArea.top, 0, 1), 0,1))
+ self.movingInstrument.Touch = True
+ elif event.type == pygame.MOUSEBUTTONUP:
+ self.movingInstrument = False
+ elif platform == 'Sugar' and event.type == mesh.CONNECT:
+ log.info( """Connected to the mesh!| %s""", event )
+ self.connected = True
+ self.music_player.resetBeat()
+ elif event.type == pygame.KEYDOWN:
+ try:
+ iaction = self.keycode[event.key]
+ self.keyActions.append(iaction)
+ except KeyError:
+ pass
+ elif event.type == pygame.KEYUP:
+ try:
+ self.keyActions.remove(self.keycode[event.key])
+ except ValueError: pass
+ except KeyError: pass
+ elif self.connected and event.type == mesh.PARTICIPANT_ADD:
+ if not self.myself: self.myself = mesh.my_handle()
+ if event.handle == self.myself:
+ if self.sharer == "Pending": self.sharer = self.myself
+ elif len(self.occupiedInstruments) == 4:
+ pass
+ else:
+ if self.sharer == self.myself:
+ giveupInstrument = [p for p in self.PanelInstruments if p.active][0].name
+ giveupparameters = getInstrumentParameters(self.scene, giveupInstrument)
+ mesh.send_to(event.handle, "Welcome|%s|%s|%s" %(self.scene.scene_name, giveupInstrument, giveupparameters))
+ self.stealInstrument(giveupInstrument, handle = event.handle)
+ if self.connected: mesh.broadcast('Occupied|%s' %self.occupiedInstruments)
+ olpcgames.ACTIVITY.J2JToolbar.deactivate_scene_change()
+ if len(self.occupiedInstruments) >= 2 and not self._syncloop_running:
+ self._syncloop_running = True
+ self.sendSync()
+ else:
+ self.latency_checker()
+ log.info("Waiting to be assigned instrument from sharer")
+ elif self.connected and event.type == mesh.PARTICIPANT_REMOVE:
+ "return instrument to the sharer if a jammer leaves."
+ try:
+ relname = [n for n in self.occupiedInstruments if self.occupiedInstruments[n] == str(event.handle)][0]
+ relpanel = [p for p in self.PanelInstruments if p.name == relname][0]
+ del self.occupiedInstruments[relname]
+ relpanel.Touch = True
+ if self.sharer == self.myself:
+ self.music_player.mutelist.remove(relname)
+ if len(self.occupiedInstruments) == 1:
+ olpcgames.ACTIVITY.J2JToolbar.reactivate_scene_change()
+ if len(self.occupiedInstruments) <= 1:
+ self._syncloop_running = False
+ except IndexError: log.debug("Index error while removing jammer %s occ = %s" %(str(event.handle), self.occupiedInstruments))
+ except KeyError: pass
+ except ValueError: pass
+ if self.sharer == self.myself: mesh.broadcast('Occupied|%s' %self.occupiedInstruments)
+ log.info( """Removed jammer| %s""", event )
+ elif self.connected and (event.type == mesh.MESSAGE_MULTI or event.type == mesh.MESSAGE_UNI):
+ if event.handle == self.myself:
+ pass
+ else:
+ self.receiveMessage(event.content, event.handle)
+ def interfaceAction(self, iaction):
+ if iaction.startswith("Nudge"):
+ direction = iaction.split("|")[1]
+ insname = self.selectedInstrument.name
+ if direction == "Left" or direction == "Right":
+ param = self.Hparam
+ else:
+ param = self.Vparam
+ currentValue = self.scene.Params.getValue(param, insname)
+ newvalue = limit((currentValue - 0.03 if direction == "Left" or direction == "Down" else currentValue + 0.03), 0, 1)
+ self.scene.Params.setValue(param, insname, newvalue)
+ self.selectedInstrument.Touch = True
+ if newvalue == 0 or newvalue == 1:
+ try:
+ self.keyActions.remove(iaction)
+ except ValueError: pass
+ elif iaction.startswith("Instrument"):
+ ins = iaction.split('|')[1]
+ self.requestInstrument(ins)
+ try:
+ self.keyActions.remove(iaction)
+ except ValueError: pass
+ elif iaction.startswith("Parameter"):
+ pm = iaction.split('|')[1]
+ if "Modifier|Shift" in self.keyActions:
+ print "shift key is on"
+ if olpcgames.ACTIVITY:
+ olpcgames.ACTIVITY.J2JToolbar.set_vertical_parameter(pm)
+ else:
+ self.Vparam = pm
+ else:
+ print "shift key is off"
+ if olpcgames.ACTIVITY:
+ olpcgames.ACTIVITY.J2JToolbar.set_horizontal_parameter(pm)
+ else:
+ self.Hparam = pm
+ try:
+ self.keyActions.remove(iaction)
+ except ValueError: pass
+ else: pass
+ def stealInstrument(self, stealname, releasename = False, handle = False):
+ "attempts to deactivate an instrument, and make it unavailable for selection"
+ if not handle: handle = self.myself
+ if stealname == self.selectedInstrument.name:
+ log.info("ignoring request to steal %s: already active" %stealname)
+ return False
+ elif stealname in self.occupiedInstruments and not releasename:
+ log.info ("ignoring request to steal %s: already occupied and no release instrument provided" %stealname)
+ return False
+ else:
+ paneli = [pnli for pnli in self.PanelInstruments if pnli.name == stealname][0]
+ self.occupiedInstruments.update({stealname:str(handle)})
+ self.music_player.mutelist.append(stealname)
+ if releasename:
+ relname = releasename
+ relpanel = [p for p in self.PanelInstruments if p.name == relname][0]
+ try:
+ del self.occupiedInstruments[relname]
+ relpanel.Touch = True
+ self.music_player.mutelist.remove(relname)
+ except KeyError: pass
+ except ValueError: pass
+ paneli.Touch = True
+ return True
+ def requestInstrument(self, name):
+ "instrument selections should go through this first. To request an instrument, you need to give one up"
+ if name in self.occupiedInstruments:
+ log.info('failed instrument selection, as instrument currently occupied')
+ else:
+ if self.connected and (self.sharer != self.myself):
+ releasename = self.selectedInstrument.name
+ iparams = getInstrumentParameters(self.scene, releasename)
+ requestname = name
+ mesh.send_to(self.sharer, 'JammerRequest|%s|%s|%s' %(releasename, requestname, iparams))
+ else:
+ self.reselectInstruments(name)
+ if self.connected: mesh.broadcast('Occupied|%s' %self.occupiedInstruments)
+ def receiveMessage(self, instruction, handle):
+ if instruction.startswith("Welcome"):
+ messages = instruction.split("|")
+ self.sharer = handle
+ jam_scene = messages[1]
+ self.pending_instrument_assignment = [messages[2],messages[3]]
+ self.select_activity_scene(jam_scene)
+ if self.sharer != self.myself:
+ olpcgames.ACTIVITY.J2JToolbar.deactivate_scene_change()
+ elif instruction.startswith("Beat"):
+ splitvals = instruction.split('|')
+ receivedBeat = int(splitvals[1])
+ time_now = now()
+ self.beatEstimator.addBeat(receivedBeat, time_now)
+ if abs(receivedBeat - self.beatEstimator.beat_match(time_now)) > 0.17:
+ pass
+ else:
+ latency = (sum(self.latency) / len(self.latency))
+ tmult = self.music_player.tempoMult
+ latency = latency * 0.25 + 0.04 #this might be XO 1.0 specific
+ beatadvance = int(ceil(latency * 1/tmult))
+ scheduled_time = now() + ((beatadvance * tmult) - latency)
+ self.music_player.Cease()
+ self.music_player.playLoop(scheduled_time, (receivedBeat + beatadvance) % self.music_player.beatlimit)
+ elif instruction.startswith("JammerRequest"):
+ "In theory only the sharer ever gets this message"
+ split = instruction.split('|')
+ releasename = split[1]
+ requestname = split[2]
+ iparams = eval(split[3])
+ stealresult = self.stealInstrument(requestname, releasename, handle)
+ if stealresult:
+ setInstrumentParameters(self.scene, releasename, iparams)
+ rqparams = getInstrumentParameters(self.scene, requestname)
+ mesh.send_to(handle, "AuthorisedInstrument|%s|%s" %(requestname, rqparams))
+ mesh.broadcast('Occupied|%s' %self.occupiedInstruments)
+ else:
+ mesh.send_to(handle, "DeniedInstrument|%s")
+ elif instruction.startswith("AuthorisedInstrument"):
+ "In theory only a 'joiner' receives this message"
+ msg = instruction.split('|')
+ ai = msg[1]
+ params = eval(msg[2])
+ setInstrumentParameters(self.scene, ai, params)
+ self.reselectInstruments(ai)
+ elif instruction.startswith("DeniedInstrument"):
+ di = instruction.split('|')[1]
+ log.info("Instrument request for %s was denied by sharer." %di)
+ elif instruction.startswith("Occupied"):
+ insdict = eval(instruction.split('|')[1])
+ self.occupiedInstruments = insdict
+ for pni in self.PanelInstruments:
+ if pni.name in insdict:
+ pni.deactivate()
+ else:
+ pni.activate()
+ elif instruction.startswith("LateReq"):
+ id = instruction.split('|')[1]
+ mesh.send_to(handle, "LateResp|%s" %id)
+ elif instruction.startswith("LateResp"):
+ id = int(instruction.split('|')[1])
+ try:
+ t = self.latency_time_ID[id]
+ del self.latency_time_ID[id]
+ result = (now() - t) / 2
+ avglat = sum(self.latency) / len(self.latency)
+ diffs = [(val - avglat) ** 2 for val in self.latency]
+ stddev = sqrt(sum(diffs) / len(diffs))
+ if id == 0:
+ del self.latency[0]
+ self.latency.append(result)
+ elif result > (avglat + stddev):
+ pass
+ elif result < (avglat - stddev) and len(self.latency) > 6:
+ pass
+ elif len(self.latency) > 12:
+ del self.latency[0]
+ self.latency.append(result)
+ else:
+ self.latency.append(result)
+ except KeyError:
+ log.info('Unmatched time ID %s' %id)
+ else:
+ log.debug("UNKNOWN INSTRUCTION RECEIVED :%s", instruction)
+ def reselectInstruments(self, name):
+ "Swaps the instrument on screen and selects the active Panel Instruments available to this user"
+ oldInstrument = self.selectedInstrument
+ oldname = oldInstrument.name
+ if (self.sharer == self.myself) or not self.connected:
+ del self.occupiedInstruments[oldname]
+ self.occupiedInstruments.update({name:str(self.myself)})
+ self.screen.blit(self.backgroundImage, oldInstrument.Rect, oldInstrument.Rect)
+ oldInstrument.Touch = False
+ self.setselectedInstrument([i for i in self.TemplateInstruments if i.name == name][0])
+ for w in self.PanelInstruments:
+ if w.name == name or w.name == oldname:
+ w.Touch = True
+ if self.connected:
+ if self.sharer == self.myself:
+ self.music_player.mutelist = [j for j in self.occupiedInstruments if j != self.selectedInstrument.name]
+ else:
+ self.music_player.mutelist = [k.name for k in self.TemplateInstruments if k.name != self.selectedInstrument.name]
+ def load_scene(self, name, key, mode, tempo, defaults = {}):
+ self.music_player.freeze()
+ self.music_player.cs.perf.Stop()
+ self.music_player.cs.perf.Join()
+ self.music_player.cs.csound.cleanup()
+ sc = City.ScenePlayer(name, key, mode, tempo, defaults)
+ mp = City.makePlayer(sc)
+ global schedEvent, now
+ schedEvent = sc.TimeQueue.schedEvent
+ now = sc.cs.perfTime
+ self.scene = sc
+ self.music_player = mp
+ self.music_player.playLoop(now())
+ self.selectedInstrument.Touch = True
+ self.music_player.picture_cycle = [self, True]
+
+ def select_activity_scene(self, scene_name, key = None, mode = None, tempo = None, defaults = None):
+ current_scene_name = self.scene.scene_name
+ if scene_name == current_scene_name:
+ if self.pending_instrument_assignment:
+ self.receiveMessage("AuthorisedInstrument|%s|%s" %(self.pending_instrument_assignment[0], self.pending_instrument_assignment[1]), self.myself)
+ elif not olpcgames.ACTIVITY:
+ k = (key if key else 'G')
+ m = (mode if mode else 'major')
+ t = (int(tempo) if tempo else 164)
+ d = (eval(defaults) if defaults else {})
+ self.load_scene(scene_name, k,m,t,d)
+ else:
+ toolbar = olpcgames.ACTIVITY.J2JToolbar
+ try:
+ ndx = [n[0] for n in toolbar.scenes].index(scene_name)
+ toolbar._Scene_combo.combo.set_active(ndx)
+ except ValueError:
+ log.info('request to change to unknown scene: %s', scene_name)
+ def latency_checker(self):
+ if self.sharer:
+ self.latency_time_ID[self.latency_counter] = now()
+ mesh.send_to(self.sharer, "LateReq|%s" %self.latency_counter)
+ self.latency_counter += 1
+ if self.latency_counter < 10:
+ Timer(3.5, self.latency_checker, ()).start()
+ elif self.latency_counter < 50:
+ Timer(6.75, self.latency_checker, ()).start()
+ else:
+ log.info('turning off latency checking')
+
+#pygame main loop
+def main():
+ # check automatic power management
+ try:
+ sugar_pm_check = subprocess.Popen("sugar-control-panel -g automatic_pm", shell=True, stdout=subprocess.PIPE)
+ sugar_pm_result = sugar_pm_check.communicate()[0]
+ if sugar_pm_result.startswith("on"):
+ subprocess.Popen("sugar-control-panel -s automatic_pm off", shell=True)
+ _spm_off = True
+ else:
+ _spm_off = False
+ except OSError:
+ _spm_off = False
+ log.info("Failed to detect and set automatic power management")
+ screenSize_X, screenSize_Y = (olpcgames.ACTIVITY.game_size if platform=="Sugar" else (1024,640))
+ toolbarheight = 45
+ screen = pygame.display.set_mode((screenSize_X, screenSize_Y - toolbarheight))
+ a_,b_,c_,d_ = pygame.cursors.load_xbm("arrow40b.xbm", "arrow40b-mask.xbm")
+ pygame.mouse.set_cursor(a_,b_,c_,d_)
+ jam = jamScene(screen, tempo = 120)
+ jam.runloop()
+ pygame.quit()
+ jam.music_player.freeze()
+ jam.music_player.cs.perf.Stop()
+ jam.music_player.cs.csound.cleanup()
+ if _spm_off: subprocess.Popen("sugar-control-panel -s automatic_pm on", shell=True)
+
+if __name__ == '__main__':
+ logging.basicConfig()
+ print "running as main"
+ main()
+
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..c86765f
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+from sugar.activity import bundlebuilder
+if __name__ == "__main__":
+ bundlebuilder.start("Jam2Jam")