Initial commit from version 1e-99
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+The Free Software Foundation has exempted Bash from the requirement of
+Paragraph 2c of the General Public License. This is to say, there is
+no requirement for Bash to print a notice when it is started
+interactively in the usual way. We made this exception because users
+and standards expect shells not to print such messages. This
+exception applies to any program that serves as a shell and that is
+based primarily on Bash as opposed to other GNU software.
+
+ 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) 19yy <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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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.
+beatscape - the red-headed stepchild of all vertical column rhythm games
+
+beatscape is a simulator for vertical column rhythm games. Right now, it
+simulates 7-key beatmania IIDX (and, by induction, 5-key beatmania), but
+plans to support the whole line from Pop'n Music to Keyboardmania. It
+does *not* simulate any arrow games (DDR, ITG, Pump it Up, etc). If you
+want that, go use StepMania. Right now it reads BMS/BME (pretty well)
+and StepMania SM (poorly).
+
+beatscape requires:
+ pygame of a recent vintage (I'm using 1.7.1)
+ Python 2.4
+ a little bit of a clue
+
+USAGE
+
+Add your songs to the 'songs' folder. There is no restriction on the
+placement of files, other than that all files referenced by a keyfile
+(WAV,BMP,etc) must be in the same folder as that keyfile. I like to
+arrange my keyfiles logically by source, but you can do whatever you
+want.
+
+beatscape does not automatically index your music files (yet), so you
+will have to do this manually each time you add or remove songs by
+running 'mksongcache.py'.
+
+beatscape accepts a command line argument specifying a keyfile to play.
+This is handy if you're testing a keyfile and don't want to navigate the
+menus.
+
+FILES
+
+config.py
+ This lists configuration variables for various aspects of the
+ game, such as timings and button assignments. Timings are set to
+ what I think is a reasonable approximation of the console
+ versions of IIDX, and button assignments should work with your
+ standard IIDX controller hooked up via a PSX -> USB adapter.
+ Other controllers and adapters will likely require tweaking.
+
+There is not (as yet) any per-user configuration or way to install
+beatscape system-wide. Feel free to contribute. :)
+
+BUGS
+
+Oh, god, too many to mention, but these are extremely noticable:
+* bpm-change problems on some songs (notably both era mixes)
+* sometimes a song will just decide not to work after you've selected
+ it. If you select it again, it will probably work.
+* A lot of undocumented features of the BMS/BME format are still
+ unhandled, and will likely cause errors or crashes. If anyone can
+ point me to a definitive BMS/BME reference, I'd be much obliged!
+* Sometimes samples will just fail to play. I'm not yet sure if this is
+ a fault of my engine or of pygame.
+* There's no way to adjust any game options, especially speed (without
+ hacking the source).
+* It's visually abominable.
+
+LICENSE
+
+beatscape is released under the GPLv2. Details can be found in the COPYING file.
+
+OTHER
+
+beatscape might run on windows. I've only tested this a little bit, and it does
+run, but the sound is usually lagged and choppy.
+
+CONTACT
+
+I can be contacted at bytex64@bytex64.net if you have any bug reports,
+questions, fixes, or other contributions.
+
+Thanks, and have fun!
+import pygame
+from bmevent import *
+from keyhandler import *
+
+class AccuracyGraph:
+ linecolor = (255,255,0)
+ tickcolor = (200,200,200)
+ accpoints = [-90,-60,-30,-15,0,15,30,60,90]
+
+ def __init__(self,rect):
+ self.screen = pygame.display.get_surface()
+ self.disprect = rect
+ self.datapoints = [0]
+ self.ratio = rect.height / 100.0
+ self.tickpoints = map(lambda y: (y * self.ratio) + (rect.height/2), self.accpoints)
+ self.n_hits = 0
+ self.total = 0
+
+ def addpoint(self,point):
+ self.datapoints[0] = point
+ self.total += point
+ self.n_hits += 1
+ return self.total / float(self.n_hits)
+
+ def draw(self):
+ self.screen.fill((0,0,0), self.disprect)
+ for t in self.tickpoints:
+ y = self.disprect.top + t
+ pygame.draw.line(self.screen, self.tickcolor,
+ (self.disprect.left, y), (self.disprect.right-1, y))
+ if abs(self.datapoints[0]) > 100:
+ return
+ y = self.disprect.height / 2 + self.disprect.top + (self.datapoints[0] * self.ratio)
+ pygame.draw.line(self.screen, self.linecolor,
+ (self.disprect.left, y), (self.disprect.right, y),2)
+
+#!/usr/bin/python
+# beatscape is the mother of all musical button games.
+
+import sys
+import pygame
+from keygraph import *
+from keyfile import *
+from bmevent import *
+from chanman import *
+from formats import *
+from keyhandler import *
+import game
+
+try:
+ import config
+except ImportError:
+ print "Could not parse config.py. Maybe you forgot a semicolon? :-P"
+ exit(1)
+
+song = ''
+
+if len(sys.argv) > 1:
+ song = sys.argv[1]
+
+try:
+ import psyco
+ print "w00t! going psyco!"
+ psyco.full()
+except ImportError:
+ pass
+
+pygame.mixer.pre_init(44100, -16, True, 1)
+pygame.init()
+opts = pygame.FULLSCREEN | pygame.DOUBLEBUF | pygame.HWSURFACE
+pygame.display.set_mode((640,480), opts)
+screen = pygame.display.get_surface()
+
+for n in range(0,pygame.joystick.get_count()):
+ pygame.joystick.Joystick(n).init();
+
+if song:
+ # For now, assume IIDX. We'll make this smarter later.
+ game.playgame(gametypes['IIDX'],song)
+ exit(0)
+
+from mainmenu import *
+from fileselect import *
+
+l = ['mainmenu']
+
+while 1:
+ obj = None
+
+ if l[0] == 'mainmenu':
+ obj = MainMenu()
+ elif l[0] == 'fileselect':
+ obj = FileSelect()
+ elif l[0] == 'play':
+ obj = game.Game(l[1])
+ elif l[0] == 'quit':
+ sys.exit(0)
+
+ l = obj.run()
+ # Flush events before switching to the next mode
+ pygame.event.clear()
+
+pygame.quit()
+BME_BGM = 0x01
+BME_TEMPO = 0x02
+BME_BGA = 0x04
+BME_CHPOOR = 0x08
+BME_NOTE1 = 0x10
+BME_NOTE2 = 0x20
+BME_HIT = 0x40
+BME_STOP = 0x80
+BME_LONGMEASURE = 0x100
+
+class BMEvent:
+ def __init__(self,beat,type,key=None,dataref=None):
+ self.beat = beat
+ self.type = type
+ self.key = key
+ self.dataref = dataref
+
+ def __str__(self):
+ s = "BMEvent:\n"
+ s += " beat=" + str(self.beat) + "\n"
+ t = { BME_BGM: "BGM",
+ BME_TEMPO: "TEMPO",
+ BME_BGA: "BGA",
+ BME_CHPOOR: "CHANGE POOR",
+ BME_NOTE1: "NOTE P1",
+ BME_NOTE2: "NOTE P2"
+ }[self.type]
+ s += " type=" + t + "\n"
+ if self.key:
+ s += " key=" + str(self.key) + "\n"
+ if self.dataref:
+ try:
+ s += " dataref=" + hex(self.dataref) + "\n"
+ except TypeError:
+ s += " dataref=" + str(self.dataref) + "\n"
+ return s
+from bmevent import *
+import pygame
+
+class ChannelManager:
+ keyfile = None
+ chanmap = None
+
+ def __init__(self,keyfile):
+ self.keyfile = keyfile
+ self.chanmap = [pygame.mixer.Channel(n) for n in range(0,self.keyfile.numkeys)]
+ pygame.mixer.set_num_channels(64)
+
+ def play(self,bme):
+ if bme.type & (BME_NOTE1 | BME_NOTE2 | BME_BGM | BME_HIT):
+ if bme.dataref > 0 and bme.dataref in self.keyfile.wavs:
+ self.keyfile.wavs[bme.dataref].play()
+
+# Configuration. Edit this file by hand, but beware, it is a python file
+# and the game won't start if it does not parse correctly.
+
+# These are modules necessary for certain constants used here
+from pygame.locals import *
+from constants import *
+
+# Main config, for global options
+librarypath = "/media/audio/BMS/library"
+keytimeout = 250 # milliseconds
+
+# Game config, for various game types
+
+bm = {
+ 'format': 'IIDXFormat',
+ 'keymap':
+ {K_SPACE:0, K_z:1, K_s:2, K_x:3, K_d:4, K_c:5, K_f:6, K_v:7},
+ 'jsbuttonmap':
+ { 3: 1, 6: 2, 2: 3, 7: 4, 1: 5, 4: 6 },
+ 'jsaxismap':
+ { (0,-1.0): 7, (0,0.0): 7, (1,-1.0): 0, (1,0.0): 0, (1,1.0): 0 },
+ 'timings':
+ #[(15,JUDGE_FGREAT), (35,JUDGE_GREAT), (50,JUDGE_GOOD), (80,JUDGE_BAD), (100,JUDGE_POOR)]
+ [(20,JUDGE_FGREAT), (42,JUDGE_GREAT), (70,JUDGE_GOOD), (250,JUDGE_BAD), (1000,JUDGE_POOR)]
+}
+
+popn = {
+ 'format': 'PopnFormat',
+}
+
+# game type mapping
+gametypes = {
+ 'beatmania': bm, # For now
+ 'IIDX': bm,
+ 'Popn': popn
+}
+# Constants used everywhere
+
+JUDGE_FGREAT = 1
+JUDGE_GREAT = 2
+JUDGE_GOOD = 3
+JUDGE_BAD = 4
+JUDGE_POOR = 5
+JUDGE_NA = 6
+import pygame
+from pygame.locals import *
+import config
+
+LEFT = 0
+RIGHT = 1
+UP = 2
+DOWN = 3
+OK = 4
+CANCEL = 5
+OPTION = 6
+
+keymap = {K_UP: UP, K_DOWN: DOWN, K_LEFT: LEFT, K_RIGHT: RIGHT, K_RETURN: OK, K_ESCAPE: CANCEL}
+jsamap = {(0,-1.0): OK, (1,-1.0): DOWN, (1,1.0): UP }
+jsbmap = {3: OK, 6: CANCEL, 2: OK, 7: CANCEL, 1: OK, 4: CANCEL, 10: OPTION, 9: OK}
+
+def parseevent(e):
+ try:
+ if e.type == KEYDOWN:
+ return keymap[e.key]
+ elif e.type == JOYBUTTONDOWN:
+ return jsbmap[e.button]
+ elif e.type == JOYAXISMOTION:
+ if e.value != 0.0:
+ return jsamap[(e.axis,e.value)]
+ else:
+ return None
+ except KeyError:
+ return None
+
+last_action = None
+last_action_time = 0
+
+def getaction():
+ global last_action, last_action_time
+
+ e = pygame.event.poll()
+ if e.type != NOEVENT:
+ last_action = parseevent(e)
+ if last_action:
+ last_action_time = pygame.time.get_ticks()
+ return last_action
+ else:
+ if pygame.time.get_ticks() - last_action_time > config.keytimeout:
+ last_action_time = pygame.time.get_ticks()
+ # Only repeat for directionals
+ if last_action in (LEFT,RIGHT,UP,DOWN):
+ return last_action
+ return None
+import pygame
+import cPickle
+import os, os.path, re, math
+
+from keyfile import *
+from fx import *
+import event
+
+fileroot = "songs"
+songcache = 'songcache.pickle'
+possave = 0
+
+def mksongcache():
+ songs = FileNode()
+ songs.crawl(fileroot)
+ f = open(songcache,'w')
+ cPickle.dump(songs,f)
+ f.close()
+ return songs
+
+filetile = pygame.image.load("gfx/filetile.png")
+
+class SelectWidget(FX):
+ color = (240,240,255)
+ basecolor = (200,0,0)
+ fadetime = 700
+ size = (400,30)
+
+ OFF = 0
+ ON = 1
+ FADING = 2
+
+ def __init__(self,text,location=(0,0)):
+ FX.__init__(self)
+ f = pygame.font.Font('font/Nano.ttf',16)
+ self.text = f.render(text,True,self.color)
+ self.surface = pygame.Surface(self.size).convert_alpha()
+ self.surface.fill( (0,0,0,0) )
+ self.surface.blit(filetile,(0,0))
+ self.surface.blit(self.text, (9,7))
+ self.pointlist = [(10,0),(self.size[0]-2,0),(self.size[0]-2,self.size[1]-2),(0,self.size[1]-2),(0,10)]
+ self.state = self.OFF
+ self.location = location
+
+ def off(self):
+ if self.state == self.ON:
+ self.state = self.FADING
+ self.fade_t = pygame.time.get_ticks()
+
+ def on(self):
+ self.state = self.ON
+
+ def draw(self,t):
+ self.screen.blit(self.surface,self.location)
+ color = self.basecolor
+ if self.state == self.ON:
+ color = self.color
+ if self.state == self.FADING:
+ dt = pygame.time.get_ticks() - self.fade_t
+ if dt < self.fadetime:
+ alpha = math.exp(-0.003*dt)
+ color = [(1.0-alpha) * self.basecolor[n] + alpha * self.color[n] for n in range(0,3)]
+ else:
+ self.state = self.OFF
+ pointlist = [(x[0] + self.location[0],x[1] + self.location[1]) for x in self.pointlist]
+ pygame.draw.lines(self.screen,color,True,pointlist,2)
+
+# This would look a lot less ugly if only there were something like
+# perl's cmp and <=> operators.
+# FUCK YOU PYTHON WHY DOES THIS HAVE TO BE SO COMPLICATED!?!?!?!
+def WTFsort(a,b):
+ if a.type == a.DIRECTORY and b.type == b.DIRECTORY:
+ if a.name > b.name:
+ return 1
+ elif a.name < b.name:
+ return -1
+ else:
+ return 0
+ elif a.type == a.DIRECTORY and b.type == b.FILE:
+ return 1
+ elif a.type == a.FILE and b.type == b.DIRECTORY:
+ return -1
+ else:
+ if a.info['player'] > b.info['player']:
+ return 1
+ elif a.info['player'] < b.info['player']:
+ return -1
+ else:
+ if not 'playlevel' in a.info:
+ return -1
+ if not 'playlevel' in b.info:
+ return 1
+ if a.info['playlevel'] > b.info['playlevel']:
+ return 1
+ elif a.info['playlevel'] < b.info['playlevel']:
+ return -1
+ else:
+ if not 'total' in a.info:
+ return -1
+ if not 'total' in b.info:
+ return 1
+ if a.info['total'] > b.info['total']:
+ return 1
+ elif a.info['total'] < b.info['total']:
+ return -1
+ else:
+ return 0
+
+filematch = re.compile('\.(bms|bme|sm)$',re.I)
+
+class FileNode:
+ DIRECTORY = 0
+ FILE = 1
+
+ def __init__(self):
+ self.children = []
+ self.open = False
+ self.info = None
+ self.widget = None
+
+ def crawl(self,path):
+ print "Crawling " + path
+ self.path = path
+ if os.path.isdir(path):
+ self.type = self.DIRECTORY
+ self.name = os.path.basename(path)
+ files = filter(lambda x: os.path.isdir(os.path.join(path,x)) or filematch.search(x), os.listdir(path))
+ for f in files:
+ c = FileNode()
+ if c.crawl(os.path.join(path,f)):
+ self.children.append(c)
+ self.children.sort(WTFsort)
+ elif os.path.isfile(path):
+ self.type = self.FILE
+ self.info = kf_info(path)
+ self.name = self.info['title']
+ else:
+ print path + " isn't a file or a directory. Ignoring."
+ return False
+ return True
+
+ def update(self):
+ for c in self.children:
+ if not os.path.exists(c.path):
+ self.children.remove(c)
+ else:
+ c.update()
+
+ def add(self,child):
+ self.children.append(child)
+
+ def tolist(self,depth=0):
+ self.depth = depth
+ l = [self]
+ if self.open:
+ for c in self.children:
+ r = c.tolist(depth+1)
+ for i in r:
+ l.append(i)
+ return l
+
+ def getwidget(self):
+ if not self.widget:
+ self.widget = SelectWidget(self.name)
+ return self.widget
+
+ def nukewidgets(self):
+ self.widget = None
+ for c in self.children:
+ c.nukewidgets()
+
+class FileSelect:
+
+ def __init__(self):
+ self.screen = pygame.display.get_surface()
+ try:
+ f = open(songcache,'r')
+ self.songs = cPickle.load(f)
+ f.close()
+ except IOError:
+ self.songs = mksongcache()
+ except cPickle.UnpicklingError:
+ self.songs = mksongcache()
+
+ self.period = 428
+ self.songlist = self.songs.tolist()
+ try:
+ self.position = self.songlist.index(self.songs.current)
+ except AttributeError:
+ self.position = 0
+ except ValueError:
+ self.position = 0
+
+ self.fslabel = Text('file select','font/Nano.ttf',30,(330,30),(255,0,0))
+ self.fadebottom = pygame.Surface((640,200)).convert_alpha()
+ for n in range(0,200):
+ alpha = int((1.0 - math.exp(-n/30.0)) * 255.0)
+ color = (0,0,0,alpha)
+ pygame.draw.line(self.fadebottom,color,(0,n),(640,n))
+ self.labels = {}
+ self.olabels = {}
+ self.olabels['title'] = Text('title','font/Nano.ttf',12, (20,320),(255,130,130))
+ self.labels['title'] = Text('','font/Nano.ttf',18, (30,330),(230,230,255))
+ self.olabels['artist'] = Text('artist','font/Nano.ttf',12, (20,360),(255,130,130))
+ self.labels['artist'] = Text('','font/Nano.ttf',16, (30,370),(230,230,255))
+ self.olabels['genre'] = Text('genre','font/Nano.ttf',12, (20,390),(255,130,130))
+ self.labels['genre'] = Text('','font/Nano.ttf',16, (30,400),(230,230,255))
+ self.olabels['playlevel'] = Text('difficulty','font/Nano.ttf',12, (20,420),(255,130,130))
+ self.labels['playlevel'] = Text('','font/Nano.ttf',16, (30,430),(230,230,255))
+ self.olabels['bpm'] = Text('BPM','font/Nano.ttf',12, (20,450),(255,130,130))
+ self.labels['bpm'] = Text('','font/Nano.ttf',16, (30,460),(230,230,255))
+ self.calcsongwidgets()
+
+ def draw(self,t):
+ self.screen.fill( (30,0,0) )
+ l = len(self.songlist)
+ for nu in range(self.position-4,self.position+8):
+ n = nu % l
+ self.songlist[n].getwidget().draw(t)
+
+ self.screen.blit(self.fadebottom,(0,280))
+ for k in self.olabels.keys():
+ self.olabels[k].draw(t)
+ for k in self.labels.keys():
+ self.labels[k].draw(t)
+ self.fslabel.draw(t)
+
+ def calcsongwidgets(self):
+ l = len(self.songlist)
+ for nu in range(self.position-4,self.position+8):
+ n = nu % l
+ w = self.songlist[n].getwidget()
+ w.location = (10 + self.songlist[n].depth * 20, 150 - (self.position - nu)*30)
+ if n == self.position:
+ w.on()
+ if self.songlist[n].type == FileNode.DIRECTORY:
+ for tag in ('artist','genre','playlevel','bpm'):
+ self.labels[tag].settext('')
+ self.labels['title'].settext(self.songlist[n].name)
+ else:
+ for tag in ('title','artist','genre','bpm'):
+ self.labels[tag].settext(self.songlist[n].info[tag])
+ try:
+ playlevel = int(self.songlist[n].info['playlevel'])
+ self.labels['playlevel'].settext(("*" * playlevel) + "(" + str(playlevel) + ")")
+ except ValueError:
+ self.labels['playlevel'].settext(str(self.songlist[n].info['playlevel']))
+ else:
+ w.off()
+
+ def run(self):
+ global possave
+ decided = 0
+ pygame.mixer.music.load('snd/0x01.ogg')
+ pygame.mixer.music.set_volume(0.75)
+ pygame.mixer.music.play(-1)
+ stab = pygame.mixer.Sound('snd/stab.ogg')
+ stab.set_volume(0.25)
+ start_t = pygame.time.get_ticks()
+
+ while decided == 0:
+ t = pygame.time.get_ticks() - start_t
+ act = event.getaction()
+ while act:
+ if act == event.CANCEL:
+ return ['mainmenu']
+ elif act == event.DOWN:
+ self.position += 1
+ self.calcsongwidgets()
+ elif act == event.UP:
+ self.position -= 1
+ self.calcsongwidgets()
+ elif act == event.OK:
+ if self.songlist[self.position].type == FileNode.DIRECTORY:
+ self.songlist[self.position].open = not self.songlist[self.position].open
+ self.songlist = self.songs.tolist()
+ self.calcsongwidgets()
+ else:
+ decided = self.position
+ act = event.getaction()
+
+ self.position = self.position % len(self.songlist)
+ self.draw(t)
+ pygame.display.flip()
+
+ pygame.mixer.music.fadeout(1500)
+ stab.play()
+
+ self.songs.nukewidgets()
+ self.songs.current = self.songlist[self.position]
+ f = open(songcache,'w')
+ cPickle.dump(self.songs,f)
+ f.close()
+
+ #possave = self.position
+ return ['play',self.songlist[decided].path]
+import pygame
+from keygraph import *
+from keyhandler import *
+from rotowidget import *
+from fx import *
+from constants import *
+
+red = (255,30,30)
+offwhite = (245,245,245)
+blue = (30,30,255)
+green = (30,255,30)
+black = (0,0,0)
+darkgray = (20,20,20)
+ggray = (200,255,200)
+
+class IIDXFormat:
+ keyfile = None
+ keystyle = [(1.75,red,black,ggray), (1,offwhite,darkgray,blue),
+ (0.8,blue,black,green), (1,offwhite,darkgray,blue),
+ (0.8,blue,black,green), (1,offwhite,darkgray,blue),
+ (0.8,blue,black,green), (1,offwhite,darkgray,blue)]
+
+ def __init__(self,keyfile):
+ self.keyfile = keyfile
+ self.kg = (KeyGraph(keyfile,self.keystyle,1,pygame.Rect(0,0,192,400)),
+ KeyGraph(keyfile,self.keystyle,2,pygame.Rect(448,0,192,400)) )
+ self.animpic = None
+ self.screen = pygame.display.get_surface()
+ self.roto = RotoWidget((30,430),25,1.0)
+ self.songlength = keyfile.length()
+ self.groove = 20.0
+ self.groovetext = GlyphText((60,415))
+ self.accuracy = 0
+ self.nacc = 0
+
+ def setproperties(self,properties):
+ if properties.has_key('judgement'):
+ j = properties['judgement']
+ if j == JUDGE_FGREAT:
+ self.groove += 1.0
+ elif j == JUDGE_GREAT:
+ self.groove += 0.7
+ elif j == JUDGE_GOOD:
+ self.groove += 0.2
+ elif j == JUDGE_BAD:
+ self.groove -= 2.0
+ elif j == JUDGE_POOR:
+ self.groove -= 6.0
+ if self.groove > 100.0:
+ self.groove = 100.0
+ elif self.groove < 0.0:
+ self.groove = 0.0
+ if 'accuracy' in properties:
+ self.accuracy += properties['accuracy']
+ self.nacc += 1
+ print "accuracy:", self.accuracy / float(self.nacc)
+ self.kg[0].setproperties(properties)
+
+ def draw(self, t, bmelist):
+ b = self.keyfile.eval_beatfunc(t)
+ for bme in bmelist:
+ if bme.type == BME_BGA and bme.dataref in self.keyfile.bmps:
+ self.animpic = self.keyfile.bmps[bme.dataref]
+ self.screen.fill( (20,20,20) )
+ for k in self.kg:
+ k.t = t
+ k.draw()
+ if self.animpic:
+ self.screen.fill((0,0,0),(192,112,256,256))
+ self.screen.blit(self.animpic,(192,112))
+ self.roto.pulsev = self.groove
+ self.roto.arcv = (float(t) / self.songlength) * 100
+ self.roto.draw(b)
+ self.groovetext.settext("%02d" % self.groove)
+ self.groovetext.draw(t)
+ pygame.display.update()
+
+lightgray = (230,230,230)
+graybg = (30,30,30)
+yellow = (230,230,0)
+yellowbg = (30,30,0)
+green = (30,255,30)
+greenbg = (0,30,0)
+blue = (0,0,230)
+bluebg = (0,0,30)
+redbg = (30,0,0)
+
+class PopnFormat:
+ keyfile = None
+ keystyle = [(1,lightgray,graybg), (0.7,yellow,yellowbg),
+ (1,green,greenbg), (0.7,blue,bluebg), (1,red,redbg),
+ (0.7,blue,bluebg), (1,green,greenbg), (0.7,yellow,yellowbg),
+ (1,lightgray,graybg)]
+
+ def __init__(self,keyfile):
+ self.keyfile = keyfile
+ self.kg = KeyGraph(keyfile,self.keystyle,1,pygame.Rect(256,0,384,400))
+ self.kg.bordercolor = black
+
+ self.animpic = None
+ self.screen = pygame.display.get_surface()
+
+ def draw(self, t, bmelist):
+ for bme in bmelist:
+ if bme.type == BME_BGA:
+ self.animpic = self.keyfile.bmps[bme.dataref]
+ self.screen.fill( (20,20,20) )
+ self.kg.t = t
+ self.kg.draw()
+ if self.animpic:
+ self.screen.fill((0,0,0),(0,112,256,256))
+ self.screen.blit(self.animpic,(0,112))
+ pygame.display.update()
+
+import pygame
+import math
+import os, os.path
+
+ALIGN_LEFT = 0
+ALIGN_RIGHT = 1
+
+# FX is the abstract base class for all "effects" objects. They have two
+# defining features:
+#
+# 1) The FX base class sets up self.screen so that you can draw to the
+# screen. It is then important to call FX.__init__(self) in your
+# __init__ routine.
+# 2) Each FX object has a draw(self,t) method that draws itself to the
+# screen (possibly with appearance based on the current time).
+
+class FX:
+ def __init__(self):
+ self.screen = pygame.display.get_surface()
+
+ def draw(self,t):
+ pass
+
+class Blank(FX):
+ def draw(self,t):
+ self.screen.fill((0,0,0))
+
+class Image(FX):
+ align = ALIGN_LEFT
+
+ def __init__(self,file,location=(0,0)):
+ FX.__init__(self)
+ self.surface = pygame.image.load(file).convert_alpha()
+ self.location = location
+
+ def draw(self,t):
+ if self.align == ALIGN_RIGHT:
+ rl = (self.location[0] - self.surface.get_width(),self.location[1])
+ self.screen.blit(self.surface,rl)
+ else:
+ self.screen.blit(self.surface,self.location)
+
+class Text(Image):
+ def __init__(self,text,font,size,location=(0,0),color=(255,255,255)):
+ FX.__init__(self)
+ self.font = pygame.font.Font(font,size)
+ self.location = location
+ self.color = color
+ self.settext(text)
+
+ def settext(self,text):
+ f = self.font.render(text,True,(0,0,0))
+ self.surface = pygame.surface.Surface((f.get_width()+2,f.get_height()+2)).convert_alpha()
+ self.surface.fill((0,0,0,0))
+ self.surface.blit(f,(2,2))
+ f = self.font.render(text,True,self.color)
+ self.surface.blit(f,(0,0))
+
+textglyphs = {}
+
+class GlyphText(FX):
+ def __init__(self,location=(0,0),glyphdir='gfx/glyph'):
+ FX.__init__(self)
+ if not textglyphs:
+ l = os.listdir(glyphdir)
+ for g in l:
+ c = g.split('.')[0]
+ textglyphs[c] = pygame.image.load(os.path.join(glyphdir,g)).convert_alpha()
+ self.location = location
+ self.text = ''
+
+ def settext(self,str):
+ self.text = str
+
+ def draw(self,t):
+ (x,y) = self.location
+ for c in self.text:
+ if textglyphs.has_key(c):
+ self.screen.blit(textglyphs[c],(x,y))
+ x += textglyphs[c].get_width()
+
+class PulseLine(FX):
+ r = -0.02
+
+ def __init__(self, pointlist, color, period, maxwidth = 8, minwidth = 1):
+ FX.__init__(self)
+ self.period = period
+ self.color = color
+ if maxwidth:
+ self.maxwidth = maxwidth
+ if minwidth:
+ self.minwidth = minwidth
+ self.pointlist = pointlist
+ self.location = (0,0)
+
+ def draw(self,t):
+ w = (self.maxwidth - 1) * math.exp(self.r * (t % self.period)) + self.minwidth
+ pl = map(lambda x: (x[0] + self.location[0],x[1] + self.location[1]),self.pointlist)
+ pygame.draw.lines(self.screen,self.color, 0, pl, int(w))
+
+class ColumnPulse(FX):
+ r = -0.03
+
+ def __init__(self,rect,color):
+ FX.__init__(self)
+ self.rect = rect
+ self.color = color
+ self.st = 0
+ self.surf = []
+ t = 0
+ a = 1.0
+ while a > 0.01:
+ s = pygame.Surface(self.rect.size).convert_alpha()
+ s.lock()
+ s.fill( (0,0,0,0) )
+ for n in range(0,self.rect.height):
+ c = (self.color[0],self.color[1],self.color[2],float(n) / self.rect.height * a * 255.0)
+ w = (self.rect.width / 2) * (1.0 - a)
+ pygame.draw.line(s,c,(w,n),(self.rect.width-w,n))
+ #s.fill(c,(0,n,self.rect.width,1))
+ s.unlock()
+ self.surf.append(s)
+ t += 16
+ a = math.exp(self.r * t)
+
+ self.hit = 0
+
+ def down(self):
+ self.hit = 1
+
+ def up(self):
+ self.st = pygame.time.get_ticks()
+ self.hit = 0
+
+ def draw(self):
+ dt = pygame.time.get_ticks() - self.st
+ if self.hit:
+ self.screen.blit(self.surf[0],self.rect)
+ else:
+ n = dt / 16
+ if n > len(self.surf) - 1:
+ return
+ self.screen.blit(self.surf[n],self.rect)
+
+class PlaneScroll(FX):
+ surface = None
+
+ def __init__(self,file,direction,zoom=1.0,alpha=1.0):
+ FX.__init__(self)
+ ts = pygame.image.load(file)
+ if zoom != 1.0:
+ ts = pygame.transform.rotozoom(ts, 0.0, zoom)
+ self.surface = ts.convert()
+ del ts
+ if alpha < 1.0 and alpha >= 0.0:
+ self.surface.set_alpha(int(alpha * 255))
+ self.direction = direction
+
+ def draw(self,t):
+ w = self.surface.get_width()
+ h = self.surface.get_height()
+ location = self.surface.get_rect()
+ location.size = (640,480)
+ location.move_ip((t * self.direction[0]) % w, (t * self.direction[1]) % h)
+ self.screen.blit(self.surface,(0,0),location)
+ if location.right > w:
+ x = 640 - (location.right - w)
+ self.screen.blit(self.surface,(x,0),(0,location.top,640,480))
+ if location.bottom > h:
+ y = 480 - (location.bottom - h)
+ self.screen.blit(self.surface,(0,y),(location.left,0,640,480))
+ if location.bottom > h and location.right > w:
+ # x and y must be calculated from above two tests
+ self.screen.blit(self.surface,(x,y),(0,0,640,480))
+import pygame
+import thread
+
+from keygraph import *
+from keyfile import *
+from bmevent import *
+from formats import *
+from chanman import *
+from keyhandler import *
+import config
+
+events = []
+evlock = thread.allocate_lock()
+evrun = 1
+t_orig = 0
+
+def event_thread():
+ global events,evlock,t_orig
+ while evrun:
+ e = pygame.event.wait()
+ t = pygame.time.get_ticks() - t_orig
+ evlock.acquire()
+ events.append((t,e))
+ evlock.release()
+
+def event_thread_start():
+ global evrun
+ evrun = 1
+ thread.start_new_thread(event_thread,())
+
+def event_thread_stop():
+ global evrun
+ evrun = 0
+ pygame.event.post(pygame.event.Event(pygame.NOEVENT,{}))
+
+def get_events():
+ global events,evlock
+ evlock.acquire()
+ eold = events
+ events = []
+ evlock.release()
+ return eold
+
+def playgame(gameconfig,song):
+ global t_orig
+ k = kf_load(song)
+
+ dm = eval(gameconfig['format'] + '(k)');
+ cm = ChannelManager(k);
+ kh = KeyHandler(gameconfig,k);
+
+ # No background anims? No problem!
+ if not k.bmps:
+ print "IT'S PEANUT BUTTER JELLY TIME!"
+ for x in range(0,8):
+ k.bmps[x] = pygame.transform.scale(pygame.image.load("gfx/banana%d.png" % x).convert(), (256,256))
+ lastbeat = k.bmelist[-1].beat
+ i = 0.0
+ j = 0
+ while i < lastbeat:
+ k.add(BMEvent(i,BME_BGA,None,j))
+ j = (j+1) % 8
+ i += 0.25
+ k.sort()
+
+ li = BMEListIter(k.bmelist)
+ reaper_li = BMEListIter(k.bmelist)
+ t_finish = k.length() + 5000
+
+ t_orig = pygame.time.get_ticks()
+ try:
+ if k.offset < 0:
+ t_orig -= int(k.offset * 1000)
+ except NameError:
+ pass
+ b = b_l = k.eval_beatfunc(t_orig)
+
+ event_thread_start()
+
+ animpic = None
+ while 1:
+ t = pygame.time.get_ticks() - t_orig
+ b = k.eval_beatfunc(t)
+ print t,b,b_l
+ db = b - b_l
+ b_l = b
+ li.goto(b)
+ bmelist = li.window(db)
+ dm.draw(t + 45, bmelist)
+ for bme in bmelist:
+ if bme.type == BME_BGM:
+ cm.play(bme);
+ if bme.type == BME_TEMPO:
+ bpm = bme.dataref
+ kh.setproperties({'bpm': bme.dataref})
+
+ for (t,e) in get_events():
+ r = kh.handle(e,t)
+ if r:
+ (hitbme, properties) = r
+ if hitbme:
+ cm.play(hitbme)
+ dm.setproperties(properties)
+
+ if e.type == pygame.KEYDOWN:
+ if e.key == pygame.K_ESCAPE:
+ event_thread_stop()
+ pygame.mixer.stop()
+ return False
+ elif e.key == pygame.K_PRINT:
+ pygame.image.save(screen,"screenshot.bmp")
+
+ reaper_li.goto(b - 2.0)
+ bmelist = reaper_li.window(1.0, BME_NOTE1 | BME_NOTE2)
+ for bme in bmelist:
+ dm.setproperties({'judgement': JUDGE_POOR})
+ bme.type = BME_HIT
+
+ if t > t_finish:
+ event_thread_stop()
+ return True
+
+# Hmm. This is sort of braindead, isn't it?
+class Game:
+ def __init__(self,song):
+ self.song = song
+
+ def run(self):
+ print "Playing",self.song
+ # IIDX is hardcoded for now
+ playgame(config.gametypes['IIDX'], self.song)
+ return ['fileselect']
+import pygame
+import imp
+import os.path
+from bmevent import *
+
+class KeyFile:
+ bmelist = None
+ numkeys = 0
+
+ # Loaded from a file
+ player = None
+ genre = None
+ title = None
+ artist = None
+ stagefile = None
+ playlevel = -1
+ rank = 3
+ stagefile = None
+ volwav = 1.0
+ wavs = None
+ bmps = None
+ offset = 0
+
+ # Allows us to continuously map time to the current beat
+ beatfunc = None
+
+ # Keymapping is a sequence of keycodes, mapping the index to the
+ # key
+ def __init__(self):
+ self.bmelist = []
+ self.wavs = {}
+ self.bmps = {}
+
+ def add(self,bme):
+ self.bmelist.append(bme)
+
+ def remove(self,bme):
+ return self.bmelist.remove(bme)
+
+ def sort(self):
+ def sortfun(a,b):
+ if a.beat == b.beat:
+ return 0
+ elif a.beat > b.beat:
+ return 1
+ elif a.beat < b.beat:
+ return -1
+ self.bmelist.sort(sortfun)
+
+ def dump(self):
+ for b in self.bmelist:
+ print str(b)
+
+ # AWFUL DIRTY NO GOOD HACK
+ # (but does find the end of the song within 10ms)
+ def length(self):
+ if len(self.beatfunc) >= 2:
+ t = self.beatfunc[-2][0]
+ else:
+ t = 0
+ last_beat = self.bmelist[-1].beat
+ b = self.eval_beatfunc(t)
+ while b < last_beat:
+ t+= 10
+ b = self.eval_beatfunc(t)
+ return t
+
+ # It's time to get func-y
+ def generate_beatfunc(self):
+ self.beatfunc = []
+ bpms = filter(lambda x: x.type & (BME_TEMPO | BME_LONGMEASURE | BME_STOP), self.bmelist)
+ self.beatfunc = self.generate_beatfunc_r(bpms)
+
+ def generate_beatfunc_r(self, bpms, ct=0):
+ if len(bpms) == 0:
+ return [(3600000.0,0)]
+ beat = bpms[0].beat
+ type = bpms[0].type
+
+ if type == BME_TEMPO:
+ ms_per_beat = 60000.0 / bpms[0].dataref
+ func = lambda t: (t - ct) / ms_per_beat + beat
+ if len(bpms) == 1:
+ next = 4000
+ else:
+ next = bpms[1].beat
+ duration = (next - beat) * ms_per_beat
+ self.lastbpm = bpms[0].dataref
+ elif type == BME_LONGMEASURE:
+ # Long measures only last one measure, so we do
+ # the slow measure just like a tempo change,
+ # then add a tempo change back at the end.
+ ms_per_beat = (60000.0 / self.lastbpm) * bpms[0].dataref
+ func = lambda t: (t - ct) / ms_per_beat + beat
+
+ print "Last BPM:",self.lastbpm
+ duration = 4 * ms_per_beat
+ bpms.insert(1,BMEvent(beat+4,BME_TEMPO,0,self.lastbpm))
+ elif type == BME_STOP:
+ func = lambda t: beat
+ duration = bpms[0].dataref
+ else:
+ raise Exception("WTF? Invalid type in generate_beatfunc_r")
+ l = self.generate_beatfunc_r(bpms[1:], ct + duration)
+ l.insert(0,(ct,func))
+ return l
+
+ def eval_beatfunc(self,t):
+ for n in range(0,len(self.beatfunc)-1):
+ if self.beatfunc[n][0] <= t and self.beatfunc[n+1][0] > t:
+ return self.beatfunc[n][1](t)
+
+ def show_beatfunc(self,surface):
+ end = self.length()
+ xscale = end / surface.get_width()
+ yscale = self.bmelist[-1].beat / surface.get_height()
+
+ for t in range(0,end,100):
+ beat = self.eval_beatfunc(t)
+ if beat:
+ pygame.draw.circle(surface,(255,255,255),(t/xscale,480 - beat / yscale),1)
+ else:
+ pygame.draw.line(surface,(255,0,0), (t/140.625,0), (t/140.625,480))
+
+
+class BMEListIter:
+ bmelist = None
+
+ def __init__(self,bmelist):
+ self.bmelist = bmelist
+ self.b = 0.0
+ self.i = 0
+
+ def goto(self,b):
+ l = len(self.bmelist) - 1
+ self.b = b
+
+ while self.i > 0 and b < self.bmelist[self.i].beat:
+ self.i -= 1
+ while self.i < l and b > self.bmelist[self.i].beat:
+ self.i += 1
+
+ def window(self,db,type=None):
+ l = len(self.bmelist)
+ eb = self.b + db
+ ei = self.i
+
+ while ei < l and eb >= self.bmelist[ei].beat:
+ ei += 1
+
+ if type:
+ return filter(lambda x: x.type & type, self.bmelist[self.i:ei])
+ else:
+ return self.bmelist[self.i:ei]
+
+
+screen = None
+font = None
+
+loaders = []
+for x in ["BMloader","SMloader"]:
+ f = open("loaders/" + x + ".py")
+ loaders.append(imp.load_module(x,f,x + ".py",(".py",'r',imp.PY_SOURCE)))
+ #f.close()
+
+def vmessage(message):
+ fs = font.render(message, 0, (255,255,255),(0,0,0))
+ screen.fill((0,0,0),(0,450,640,30))
+ screen.blit(fs,(0,450))
+
+def vstatus(type,arg):
+ if type == "STAGEFILE":
+ screen.blit(arg,(0,0))
+ elif type == "WAV":
+ vmessage("Loaded WAV " + arg)
+ elif type == "BMP":
+ vmessage("Loaded BMP " + arg)
+ elif type == "TRACK":
+ vmessage("Parsing track " + str(arg))
+ elif type == "ERROR":
+ vmessage("ERROR: " + arg)
+ pygame.display.flip()
+
+def likelihood(file):
+ likelihoods = map(lambda l: l.detect(file), loaders)
+ gl = 0.0
+ gn = None
+ for n in range(0,len(likelihoods)):
+ if likelihoods[n] > gl:
+ gl = likelihoods[n]
+ gn = n
+ return gn
+
+def kf_load(file):
+ global screen,font
+ screen = pygame.display.get_surface()
+ font = pygame.font.SysFont("Helvetica Normal",30)
+ gn = likelihood(file)
+ if gn != None:
+ print "Load..."
+ kf = loaders[gn].load(file,vstatus)
+ kf.generate_beatfunc()
+ return kf
+ else:
+ return None
+ font = None
+
+def kf_info(file):
+ gn = likelihood(file)
+ d = loaders[gn].info(file)
+ d['loader.name'] = loaders[gn].name
+ d['loader.version'] = loaders[gn].version
+
+ return d
+import pygame
+from bmevent import *
+from keyhandler import *
+from keyfile import BMEListIter
+from fx import ColumnPulse
+from constants import *
+
+red = (255,30,30)
+offwhite = (245,245,245)
+blue = (30,30,255)
+black = (0,0,0)
+darkgray = (20,20,20)
+
+class KeyGraph:
+ keyfile = None
+ t = 0
+ b = 0
+ velocity = 150.0
+ li = None
+ bordercolor = (255,255,255)
+ judgemapping = {JUDGE_FGREAT: ["gfx/great.png","gfx/fgreat.png"],
+ JUDGE_GREAT: ["gfx/great.png"],
+ JUDGE_GOOD: ["gfx/good.png"],
+ JUDGE_BAD: ["gfx/bad.png"],
+ JUDGE_POOR: ["gfx/poor.png"],
+ JUDGE_NA: []}
+ colmarkmap = {JUDGE_FGREAT: "gfx/dcircle.png",
+ JUDGE_GREAT: "gfx/circle.png",
+ JUDGE_GOOD: "gfx/triangle.png",
+ JUDGE_BAD: "gfx/cross.png",
+ JUDGE_POOR: "gfx/cross.png",
+ JUDGE_NA: None}
+ numberfiles = ["gfx/glyph/0.png","gfx/glyph/1.png","gfx/glyph/2.png","gfx/glyph/3.png","gfx/glyph/4.png","gfx/glyph/5.png","gfx/glyph/6.png","gfx/glyph/7.png","gfx/glyph/8.png","gfx/glyph/9.png"]
+
+ def __init__(self, kf, keystyle, player, disprect):
+ self.numkeys = len(keystyle)
+ self.keyfile = kf
+ self.screen = pygame.display.get_surface()
+ self.disprect = disprect
+ self.vheight = disprect.height - 5;
+ self.barrect = pygame.Rect(disprect.left,
+ disprect.bottom - 4,
+ disprect.width,
+ 5)
+ if player == 1:
+ self.notefilter = BME_NOTE1
+ elif player == 2:
+ self.notefilter = BME_NOTE2
+ else:
+ print "Invalid player given to keygraph: " + kf
+ exit(1)
+ self.t = 0
+ self.b = 0
+ self.li = BMEListIter(self.keyfile.bmelist)
+ self.keystyle = []
+
+ w = 0
+ tw = sum(map(lambda x: x[0],keystyle))
+ for x in keystyle:
+ self.keystyle.append((int(w),x[1],x[2]))
+ w += (x[0] / tw) * (disprect.width - 1)
+ self.keystyle.append((int(w),None,None))
+
+ # Load gfx
+ self.judgeimg = {}
+ for k in self.judgemapping.keys():
+ self.judgeimg[k] = map(lambda y: pygame.image.load(y).convert_alpha(),self.judgemapping[k])
+ self.colmarkimg = {}
+ for k in self.colmarkmap.keys():
+ if self.colmarkmap[k]:
+ self.colmarkimg[k] = pygame.image.load(self.colmarkmap[k]).convert_alpha()
+ else:
+ self.colmarkimg[k] = None
+ self.colmarkimg[0] = None
+ self.numberimg = map(lambda y: pygame.image.load(y).convert_alpha(),self.numberfiles)
+ self.judgement = 0
+ self.kjudgement = [0 for x in range(0,self.numkeys)]
+ self.judgetime = 0
+ self.kjudgetime = [0 for x in range(0,self.numkeys)]
+ self.combo = 0
+
+ # Initialize keypulses (they are neat)
+ self.pulse = []
+ for n in range(0,len(keystyle)):
+ x = self.keystyle[n][0] + 1
+ y = self.disprect.top + 0.3 * self.disprect.height
+ w = self.keystyle[n+1][0] - self.keystyle[n][0] - 1
+ h = 0.7 * self.disprect.height - 4
+ self.pulse.append(ColumnPulse(pygame.Rect(x,y,w,h),keystyle[n][3]))
+ self.accuracy = 0
+ self.key = 0
+
+ def setproperties(self,properties):
+ try:
+ ud = properties['ud']
+ k = properties['key']
+ if ud == 1:
+ self.pulse[k].down()
+ else:
+ self.pulse[k].up()
+ self.key = k
+ except KeyError:
+ pass
+
+ try:
+ self.judgement = properties['judgement']
+ self.kjudgement[self.key] = properties['judgement']
+ self.judgetime = self.t
+ self.kjudgetime[self.key] = self.t
+ if self.judgement <= JUDGE_GOOD:
+ self.combo += 1
+ else:
+ self.combo = 0
+ except KeyError:
+ pass
+
+ try:
+ self.accuracy = properties['accuracy']
+ except KeyError:
+ pass
+
+ def numimgs(self,n):
+ imgs = []
+ while n:
+ imgs.append(self.numberimg[n % 10])
+ n = n / 10
+ imgs.reverse()
+ return imgs
+
+ def draw(self):
+ self.b = self.keyfile.eval_beatfunc(self.t)
+ for n in range(0,self.numkeys):
+ x = self.disprect.left + self.keystyle[n][0]
+ w = self.keystyle[n+1][0] - self.keystyle[n][0]
+ self.screen.fill(self.keystyle[n][2],
+ (x,self.disprect.top,w,self.disprect.height))
+ for n in range(0,self.numkeys+1):
+ pygame.draw.line(self.screen, self.bordercolor,
+ (self.disprect.left + self.keystyle[n][0], self.disprect.top),
+ (self.disprect.left + self.keystyle[n][0], self.disprect.bottom - 1) )
+
+ self.li.goto(self.b)
+ keylist = self.li.window(self.vheight / self.velocity, self.notefilter)
+ pygame.draw.rect(self.screen, (255,0,0), self.barrect)
+ distortion = 1.0
+ for k in keylist:
+ y = self.disprect.top + self.vheight - int((k.beat - self.b) * (self.velocity / distortion))
+ x = self.disprect.left + self.keystyle[k.key][0] + 1
+ w = self.keystyle[k.key+1][0] - self.keystyle[k.key][0] - 1
+ pygame.draw.rect(self.screen, self.keystyle[k.key][1], (x,y,w,5) )
+ for n in self.pulse:
+ n.draw()
+
+ if self.judgement:
+ if self.t - self.judgetime > 500:
+ self.judgement = 0
+ elif len(self.judgeimg[self.judgement]) == 0:
+ pass
+ else:
+ z = (self.t / 60) % len(self.judgeimg[self.judgement])
+ w = self.judgeimg[self.judgement][z].get_width()
+ x = self.disprect.left + (self.disprect.width / 2) - (w / 2)
+ y = self.disprect.top + self.disprect.height * 0.7
+ if self.combo >= 2:
+ imgs = self.numimgs(self.combo)
+ x -= 2
+ w += 4
+ for i in imgs:
+ x -= i.get_width() / 2
+ for i in imgs:
+ self.screen.blit(i,(x + w,y))
+ w += i.get_width()
+ self.screen.blit(self.judgeimg[self.judgement][z],(x,y))
+ # Draw accuracy bar
+ x = self.disprect.left + self.disprect.width / 2
+ y += 50
+ dx = self.accuracy
+ if dx < 0:
+ x += dx
+ dx = -dx
+ self.screen.fill((255,0,0), (x,y,dx,3))
+ for i in range(0,self.numkeys):
+ if self.t - self.kjudgetime[i] > 300:
+ self.kjudgement[i] = 0
+ else:
+ x = self.disprect.left + self.keystyle[i][0] + (self.keystyle[i+1][0] - self.keystyle[i][0] - 24) / 2
+ y = self.disprect.bottom - 30
+ if self.colmarkimg[self.kjudgement[i]]:
+ self.screen.blit(self.colmarkimg[self.kjudgement[i]],(x,y))
+from bmevent import *
+from keyfile import *
+from pygame.locals import *
+from constants import *
+
+class KeyHandler:
+ keyfile = None
+ li = None
+
+ keymap = None
+ jsbuttonmap = None
+ jsaxismap = None
+ timings = None
+
+ def __init__(self,gameconfig,keyfile):
+ self.keyfile = keyfile
+ self.li = BMEListIter(keyfile.bmelist)
+ self.lastdataref = [0,0,0,0,0,0,0,0]
+ self.li.goto(0)
+ for b in self.li.window(1.0):
+ if b.type == BME_TEMPO:
+ self.bpm = b.dataref
+ self.adjust_window()
+ break
+ self.keymap = gameconfig['keymap']
+ self.jsbuttonmap = gameconfig['jsbuttonmap']
+ self.jsaxismap = gameconfig['jsaxismap']
+ self.timings = gameconfig['timings']
+
+ def decodekey(self,event):
+ try:
+ if event.type == KEYDOWN:
+ return (self.keymap[event.key],1)
+ elif event.type == KEYUP:
+ return (self.keymap[event.key],0)
+ elif event.type == JOYBUTTONDOWN:
+ return (self.jsbuttonmap[event.button],1)
+ elif event.type == JOYBUTTONUP:
+ return (self.jsbuttonmap[event.button],0)
+ elif event.type == JOYAXISMOTION:
+ if event.value == 0.0:
+ return (self.jsaxismap[(event.axis,event.value)],0)
+ else:
+ return (self.jsaxismap[(event.axis,event.value)],1)
+ else:
+ return (None,-1)
+ except KeyError:
+ return (None,-1)
+
+ def adjust_window(self):
+ self.ms_per_beat = 60000.0 / self.bpm
+ self.tstart = 200.0 / self.ms_per_beat
+ self.tlength = 2000.0 / self.ms_per_beat
+
+ def setproperties(self,properties):
+ try:
+ self.bpm = properties['bpm']
+ self.adjust_window()
+ except KeyError:
+ pass
+
+ def handle(self,event,t):
+ b = self.keyfile.eval_beatfunc(t)
+ properties = {}
+ (k,ud) = self.decodekey(event)
+ if k == None:
+ return
+ properties['key'] = k
+ properties['ud'] = ud
+ if ud == 0:
+ return (None, properties)
+ self.li.goto(b - self.tstart)
+ bmelist = filter(lambda x: x.key == k, self.li.window(self.tlength,BME_NOTE1))
+ if not bmelist:
+ properties['judgement'] = JUDGE_NA
+ return (BMEvent(b,BME_HIT,k,self.lastdataref[k]), properties)
+
+ # Find the closest bme to our event
+ sd = self.tstart
+ cbme = None
+ for bme in bmelist:
+ d = abs(b - bme.beat)
+ if d < sd:
+ sd = d
+ cbme = bme
+ # Convert back to ms
+ sd = sd * self.ms_per_beat
+
+ if cbme:
+ for bt in self.timings:
+ if sd < bt[0]:
+ properties['judgement'] = bt[1]
+ cbme.type = BME_HIT
+ break
+ self.lastdataref[k] = cbme.dataref
+ else:
+ properties['judgement'] = JUDGE_NA
+ return (BMEvent(b,BME_HIT,k,self.lastdataref[k]),properties)
+ properties['accuracy'] = (b - cbme.beat) * self.ms_per_beat
+ return (cbme, properties)
+import pygame
+import os.path
+import re
+from bmevent import *
+from keyfile import *
+from util import *
+
+# Required information for a loader module. If this isn't here, the game
+# will crash and burn and it will be ALL YOUR FAULT.
+name = "BeMusic BMS/BME"
+version = 0.0
+
+def detect(file):
+ res = [re.compile("^#PLAYLEVEL"), re.compile("^#WAV"), re.compile("^#BMP")]
+ match = [0,0,0]
+
+ f = open(file)
+ for line in f:
+ for n in range(0,len(res)):
+ if res[n].match(line):
+ match[n] = 1
+ return sum(match) / float(len(match))
+
+def info(file):
+ d = {}
+ f = open(file,'r')
+ r = re.compile('(WAV|BMP|\d{5}:)')
+ for line in f:
+ if len(line) == 0 or line[0] != "#":
+ continue
+ line = line[1:]
+ l = line.split(' ')
+ arg = ''
+ cmd = l[0]
+ if len(l) >= 2:
+ arg = ' '.join(l[1:])
+ if not r.match(cmd):
+ d[cmd.lower()] = arg.strip()
+ return d
+
+def load(file,status=lambda x,y:None):
+ keymapping = {0:1, 1:2, 2:3, 3:4, 4:5, 5:0, 7:6, 8:7}
+ kf = KeyFile()
+ kf.numkeys = len(keymapping)
+ lastbpm = 0
+ dir = os.path.dirname(file)
+ f = open(file,'r')
+ kf.track = -1
+ for line in f:
+ line = line.strip()
+ if len(line) == 0 or line[0] != "#":
+ continue
+ line = line[1:]
+ l = line.split(' ')
+ arg = ''
+ cmd = l[0]
+ if len(l) >= 2:
+ arg = ' '.join(l[1:])
+ if cmd == "PLAYER":
+ kf.player = arg
+ elif cmd == "GENRE":
+ kf.genre = arg
+ elif cmd == "TITLE":
+ kf.title = arg
+ elif cmd == "ARTIST":
+ kf.artist = arg
+ elif cmd == "BPM":
+ try:
+ lastbpm = float(arg)
+ kf.add(BMEvent(0, BME_TEMPO, None, lastbpm))
+ except ValueError:
+ print "Invalid value for BPM"
+ #kf.ms_per_measure = 240000.0 / kf.bpm
+ elif cmd == "PLAYLEVEL":
+ try:
+ kf.playlevel = int(arg)
+ except ValueError:
+ print "Invalid value for PLAYLEVEL"
+ elif cmd == "RANK":
+ try:
+ kf.rank = int(arg)
+ except ValueError:
+ print "Invalid value for RANK"
+ elif cmd == "STAGEFILE":
+ # This will stay here for now. Eventually, this
+ # will go into the "status" routine or somewhere
+ # further up.
+ if arg:
+ kf.stagefile = loadBMP(os.path.join(dir,arg))
+ status("STAGEFILE",kf.stagefile)
+ elif cmd == "VOLWAV":
+ kf.volwav = float(arg) / 100.0
+ elif cmd[0:3] == "WAV":
+ slot = int(cmd[3:5],36)
+ if arg[-3:].lower() == 'mp3':
+ wav = loadMP3(os.path.join(dir,arg))
+ else:
+ wav = loadWAV(os.path.join(dir,arg))
+ if wav:
+ wav.set_volume(kf.volwav)
+ if wav:
+ kf.wavs[slot] = wav
+ status("WAV",arg)
+ else:
+ status("ERROR","Could not load " + arg)
+ pygame.time.wait(1500)
+ elif cmd[0:3] == "BMP":
+ slot = int(cmd[3:5],36)
+ bmp = loadBMP(os.path.join(dir,arg))
+ if bmp:
+ kf.bmps[slot] = bmp
+ status("BMP",arg)
+ else:
+ status("ERROR","Could not load " + arg)
+ elif len(cmd) > 5 and cmd[5] == ":":
+ # Hmm. Should "track" really be "measure"?
+ track = int(cmd[0:3])
+ channel = int(cmd[3:5])
+ message = cmd[6:]
+
+ if channel == 2:
+ # Channel 2 is a floating-point
+ # multiplier that changes the length of
+ # the measure.
+ kf.add(BMEvent(track*4, BME_LONGMEASURE, None, float(message)))
+ continue
+ if track != kf.track:
+ status("TRACK",track)
+ kf.track = track
+
+ v = [int(message[n*2:n*2+2],36) for n in range(0,len(message)/2)]
+ l = float(len(v)) / 4.0
+
+ bme = None
+ if channel == 1:
+ for n in range(0,len(v)):
+ if v[n] == 0:
+ continue
+ bme = BMEvent(track*4 + n / l, BME_BGM, None, v[n])
+ kf.add(bme)
+ if channel == 3:
+ v = [int(message[n*2:n*2+2],16) for n in range(0,len(message)/2)]
+ for n in range(0,len(v)):
+ # WTF is up with the low tempos?
+ if v[n] == 0 or v[n] < 30:
+ continue
+ print "new BPM: " + str(v[n])
+ bme = BMEvent(track*4 + n / l, BME_TEMPO, None, v[n])
+ kf.add(bme)
+ lastbpm = v[n]
+ if channel == 4:
+ for n in range(0,len(v)):
+ if v[n] == 0:
+ continue
+ bme = BMEvent(track*4 + n / l, BME_BGA, None, v[n])
+ kf.add(bme)
+ elif (channel >= 11 and channel <= 19) or (channel >= 21 and channel <= 29):
+ if channel >= 21 and channel <= 29:
+ type = BME_NOTE2
+ b = 21
+ else:
+ type = BME_NOTE1
+ b = 11
+ for n in range(0,len(v)):
+ if v[n] == 0 or not (channel - b) in keymapping:
+ continue
+ bme = BMEvent(track*4 + n / l, type, keymapping[channel - b], v[n])
+ kf.add(bme)
+ else:
+ print "Unknown command:",cmd
+ kf.sort()
+ f.close()
+
+ return kf
+
+import pygame
+import os.path
+import re
+from bmevent import *
+from keyfile import *
+
+# Required information for a loader module. If this isn't here, the game
+# will crash and burn and it will be ALL YOUR FAULT.
+name = "StepMania"
+version = 0.0
+
+def detect(file):
+ res = [re.compile("^#SUBTITLE"), re.compile("^#BANNER"),
+ re.compile("^#BACKGROUND"),re.compile("^#NOTES")]
+ match = [0,0,0,0]
+
+ f = open(file)
+ for line in f:
+ for n in range(0,len(res)):
+ if res[n].match(line):
+ match[n] = 1
+ return sum(match) / float(len(match))
+
+def info(file):
+ return {}
+
+def load(file,status=lambda x,y:None):
+ keymapping = {0:1, 1:2, 2:3, 3:4, 4:5, 5:0, 7:6, 8:7}
+ kf = KeyFile()
+ kf.numkeys = len(keymapping)
+ dir = os.path.dirname(file)
+ f = open(file,'r')
+ kf.track = -1
+ matcher = re.compile("#([A-Z]+):(.*);",re.DOTALL)
+ kf.stoplist = []
+ buf = ''
+ for line in f:
+ line = line.split('//')[0];
+ line = line.strip()
+ if len(line) == 0:
+ continue
+ buf += line
+ m = matcher.match(buf)
+ if m:
+ buf = '';
+ cmd = m.group(1)
+ arg = m.group(2).split(':')
+ else:
+ continue
+
+ if cmd == "TITLE":
+ kf.title = arg[0]
+ elif cmd == "SUBTITLE":
+ kf.subtitle = arg[0]
+ elif cmd == "ARTIST":
+ kf.artist = arg[0]
+ elif cmd == "BPMS":
+ bpmlist = map(lambda x: x.split('='),arg[0].split(','))
+ kf.bpm = bpmlist[0][1]
+ for b in bpmlist:
+ kf.add(BMEvent(float(b[0]), BME_TEMPO, None, float(b[1])))
+ elif cmd == "STOPS":
+ if arg[0]:
+ stoplist = map(lambda x: x.split('='),arg[0].split(','))
+ for s in stoplist:
+ print s[0]
+ kf.add(BMEvent(float(s[0]), BME_STOP, None, int(float(s[1])*1000)))
+ elif cmd == "BACKGROUND":
+ if arg[0]:
+ try:
+ kf.stagefile = pygame.image.load(os.path.join(dir,arg[0]))
+ kf.stagefile = kf.stagefile.convert()
+ status("STAGEFILE",kf.stagefile)
+ except pygame.error:
+ pass
+ elif cmd == "OFFSET":
+ kf.offset = float(arg[0])
+ kf.add(BMEvent(int(kf.offset * 1000), BME_BGM, None, 255))
+ elif cmd == "MUSIC":
+ print os.path.join(dir,arg[0])
+ try:
+ pygame.mixer.music.load(os.path.join(dir,arg[0]))
+ except pygame.error:
+ pygame.mixer.music.load(os.path.join(dir,arg[0].lower()))
+ kf.wavs[255] = pygame.mixer.music
+ elif cmd == "NOTES":
+ if arg[0] == 'dance-single' and arg[2].lower() == 'challenge':
+ parse_notes(arg[5],kf)
+ else:
+ print "Unknown command:",cmd
+ kf.sort()
+ f.close()
+
+ return kf
+
+# BUGS! This assumes a four-column chart.
+def parse_notes(str,kf):
+ tracks = str.split(',')
+ track = 0
+
+ for t in tracks:
+ l = len(t) / 4
+ for n in range(0,l):
+ for m in range(0,4):
+ k = t[n*4+m]
+ if k == 'M': # Freaking mines...
+ continue
+ k = int(k)
+ if k > 0:
+ beat = 4 * (track + (float(n)/l))
+ # Add note filter here?
+ kf.add(BMEvent(beat, BME_NOTE1, m*2+1, 1))
+ track += 1
+
+import pygame
+import thread
+from fx import *
+import event
+
+class MainMenu:
+ fx = None
+ fxlock = None
+ period = 428
+ circuitlist = [[(590,0), (550,40), (200,40), (170,70), (170,310)],
+ [(570,0), (540,30), (190,30), (160,60), (20,60), (20,220), (50,250), (50,420), (90,460), (610,460)],
+ [(0,220), (40,260), (40,480)],
+ [(180,0), (180,20), (150,50), (20,50), (20,0)]]
+ # (label, message)
+ menuitems = [("play","fileselect"), ("quit","quit")]
+ subtitle = "holy shit parallax!!!11!1"
+ version = 1e-99
+
+ def __init__(self):
+ self.fxlock = thread.allocate_lock()
+ self.fx = []
+ self.menufx = []
+ self.position = 0
+ self.bar = PulseLine([(0,0),(130,0)],(200,200,255),self.period,5)
+ thread.start_new_thread(self.fxloader,())
+
+ def fxloader(self):
+ self.fxlock.acquire()
+ self.fx.append(Blank())
+ for l in self.circuitlist:
+ o = PulseLine(l,(255,30,30),self.period)
+ self.fx.append(o)
+ self.fxlock.release()
+
+ o = Image('gfx/title.png',(200,40))
+ self.fxlock.acquire()
+ self.fx.append(o)
+ self.fxlock.release()
+
+ n = 0
+ for l in self.menuitems:
+ o = Text(l[0],'font/Nano.ttf',18,(30,70 + 20*n), (255,30,30))
+ self.fxlock.acquire()
+ self.fx.append(o)
+ self.menufx.append(o)
+ self.fxlock.release()
+ n += 1
+
+ o = Text(self.subtitle,'font/Nano.ttf',15,(600,100),(255,30,30))
+ o.align = ALIGN_RIGHT
+ self.fxlock.acquire()
+ self.fx.append(o)
+ self.fxlock.release()
+
+ o = Text("version " + str(self.version),'font/Nano.ttf',14,(610,440),(255,30,30))
+ o.align = ALIGN_RIGHT
+ self.fxlock.acquire()
+ self.fx.append(o)
+ self.fxlock.release()
+
+ o = PlaneScroll('gfx/chips_layer1.jpg',(-0.12,0.06),2.0,0.75)
+ self.fxlock.acquire()
+ self.fx.insert(1,o)
+ self.fxlock.release()
+
+ o = PlaneScroll('gfx/chips_layer2.jpg',(-0.06,0.03))
+ self.fxlock.acquire()
+ self.fx.remove(self.fx[0])
+ self.fx.insert(0,o)
+ self.fxlock.release()
+
+ def draw(self,t):
+ self.fxlock.acquire()
+ for f in self.fx:
+ f.draw(t)
+ self.fxlock.release()
+ self.bar.location = (30, 68 + 20*self.position)
+ self.bar.draw(t)
+ self.bar.location = (30, 68 + 20*self.position + 20)
+ self.bar.draw(t)
+
+ def run(self):
+ screen = pygame.display.get_surface()
+ decided = 0
+ pygame.mixer.music.load('snd/0x00.ogg')
+ pygame.mixer.music.set_volume(0.75)
+ pygame.mixer.music.play(-1)
+ stab = pygame.mixer.Sound('snd/stab.ogg')
+ stab.set_volume(0.25)
+ start_t = pygame.time.get_ticks()
+
+ while decided == 0:
+ t = pygame.time.get_ticks() - start_t
+ for e in pygame.event.get():
+ act = event.parseevent(e)
+ if act == event.CANCEL:
+ decided = 'quit'
+ elif act == event.DOWN:
+ self.position += 1
+ elif act == event.UP:
+ self.position -= 1
+ elif act == event.OK:
+ decided = self.menuitems[self.position][1]
+
+ self.position = self.position % len(self.menuitems)
+ self.draw(t)
+ pygame.display.flip()
+
+ pygame.mixer.music.fadeout(1500)
+ stab.play()
+
+ start_t2 = pygame.time.get_ticks()
+ dt = 0
+ blanksurf = pygame.surface.Surface((640,480)).convert()
+ blanksurf.fill( (0,0,0) )
+ while dt < 1500:
+ dt = pygame.time.get_ticks() - start_t2
+ if dt < 1000:
+ self.draw(pygame.time.get_ticks() - start_t)
+ blanksurf.set_alpha(int(dt / 1000.0 * 255.0))
+ screen.blit(blanksurf,(0,0))
+ else:
+ screen.fill( (0,0,0) )
+ self.menufx[self.position].draw(0)
+ if dt >= 1000:
+ blanksurf.set_alpha(int((dt-1000) / 500.0 * 255.0))
+ screen.blit(blanksurf,(0,0))
+ pygame.display.flip()
+
+ return [decided]
+#!/usr/bin/python
+
+import fileselect
+
+fileselect.mksongcache()
+
+import pygame
+from bmevent import *
+from keyhandler import *
+from math import exp,pi,sqrt
+
+# A rotowidget displays two axes of information in a rotary fashion. A
+# central "pulser" is designed to show the score, and an arc around the
+# edge shows the progress through the song.
+class RotoWidget:
+ # Color format: (level, color)
+ # If the first item's level is greater than zero, the first
+ # color is black
+ pulsecolors = [(0,(180,0,0)), (80,(50,255,0))]
+ arccolors = [(0,(200,0,200))]
+ r = -0.2
+
+ def __init__(self,center,radius,period):
+ self.screen = pygame.display.get_surface()
+ self.center = center
+ self.radius = radius
+ self.period = period
+ self.pulsev = 0
+ self.arcv = 0
+ self.t = 0
+
+ def draw(self,t):
+ rad = self.radius * (self.pulsev / 100.0) + (0.1 * self.radius) * exp(self.r * (t % self.period))
+ if rad > self.radius:
+ rad = self.radius
+
+ pc = (0,0,0)
+ for n in range(len(self.pulsecolors)-1,-1,-1):
+ if self.pulsecolors[n][0] <= self.pulsev:
+ pc = self.pulsecolors[n][1]
+ break
+ pygame.draw.circle(self.screen,pc,self.center,rad,0)
+
+ ac = (0,0,0)
+ for n in range(len(self.arccolors)-1,-1,-1):
+ if self.arccolors[n][0] <= self.arcv:
+ ac = self.arccolors[n][1]
+ break
+ pygame.draw.arc(self.screen,ac,(self.center[0]-self.radius, self.center[1]-self.radius, self.radius*2, self.radius*2),0,2*pi*(self.arcv / 100.0),3)
+
+import pygame
+import os.path
+
+import config
+
+def loadWAV(path,library=False):
+ try:
+ return pygame.mixer.Sound(path)
+ except pygame.error:
+ (dir,file) = os.path.split(path)
+ try:
+ return pygame.mixer.Sound(os.path.join(dir,file.lower()))
+ except pygame.error:
+ if library:
+ return None
+ else:
+ # Ok, try the system sample library...
+ return loadWAV(os.path.join(config.librarypath,file),True)
+
+def loadMP3(path):
+ pygame.mixer.music.load(path)
+ return pygame.mixer.music
+
+def loadBMP(path):
+ try:
+ return pygame.image.load(path).convert()
+ except pygame.error:
+ (dir,file) = os.path.split(path)
+ try:
+ return pygame.image.load(os.path.join(dir,file.lower())).convert()
+ except pygame.error:
+ return None
+