├── .gitignore ├── res ├── img │ └── logo-prealpha.png └── skin │ ├── loading.cson │ ├── playresult.cson │ └── selecting.cson ├── .gitmodules ├── libs └── sqlite3 │ └── Makefile ├── src ├── util │ ├── macros.rs │ ├── envelope.rs │ ├── std.rs │ ├── console.rs │ ├── maybe_owned.rs │ ├── filesearch.rs │ └── lex.rs ├── ext │ ├── sdl.rs │ ├── win32.rs │ └── smpeg.rs ├── gfx │ ├── color.rs │ ├── ratio_num.rs │ ├── skin │ │ ├── hook.rs │ │ ├── scalar.rs │ │ └── ast.rs │ └── surface.rs ├── ui │ ├── playresult.rs │ ├── init.rs │ ├── scene.rs │ ├── common.rs │ ├── hooks.rs │ └── viewing.rs ├── format │ ├── metadata.rs │ └── bms │ │ ├── encoding.rs │ │ ├── mod.rs │ │ ├── types.rs │ │ ├── diag.rs │ │ └── preproc.rs └── engine │ ├── resource.rs │ ├── input.rs │ └── keyspec.rs ├── .travis.yml ├── README.md ├── Cargo.toml ├── Makefile └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | *.db 2 | *.o 3 | *.a 4 | target 5 | -------------------------------------------------------------------------------- /res/img/logo-prealpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snrs/sonorous/HEAD/res/img/logo-prealpha.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libs/rust-opengles"] 2 | path = libs/rust-opengles 3 | url = https://github.com/lifthrasiir/rust-opengles-angle.git 4 | [submodule "libs/w32api-directx-standalone"] 5 | path = libs/w32api-directx-standalone 6 | url = https://github.com/lifthrasiir/w32api-directx-standalone.git 7 | -------------------------------------------------------------------------------- /libs/sqlite3/Makefile: -------------------------------------------------------------------------------- 1 | INC = sqlite3.h 2 | SRC = sqlite3.c 3 | OBJ = sqlite3.o 4 | LIB = libsqlite3.a 5 | 6 | AR ?= ar 7 | CC ?= gcc 8 | CFLAGS ?= -Os -fPIC 9 | 10 | .PHONY: all clean 11 | 12 | all: $(LIB) 13 | 14 | $(OBJ): $(SRC) $(INC) 15 | $(CC) $(CFLAGS) -c $< -o $@ 16 | 17 | $(LIB): $(OBJ) 18 | $(AR) ru $@ $(OBJ) 19 | 20 | clean: 21 | rm -rf $(OBJ) $(LIB) 22 | 23 | -------------------------------------------------------------------------------- /src/util/macros.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md for details. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or 6 | // the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! Various macros. 10 | 11 | #![macro_escape] 12 | 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | os: 3 | - linux 4 | - osx 5 | env: 6 | global: 7 | - LD_LIBRARY_PATH: /usr/local/lib 8 | - secure: L3jilKnridj1drhcGqH9g8mp3HwdK6iWGX7BSwxnrdEMJxuxAW2eK06cZw49MHwoDTqXBxfqROsA0GSMTNmuqxC58Zohlz7mCxfeH6ujPhhwW+TQl6E8hEuUKi0R07c9ofGXgEqF0ljFB0pRo88MDDhEsNotRy9VCo1gi82r744= 9 | before_install: 10 | - sudo apt-get update 11 | - sudo apt-get install libsdl1.2-dev libsdl-mixer1.2-dev libsdl-image1.2-dev libsmpeg-dev libsqlite3-dev 12 | before_script: 13 | - rustc -v 14 | - cargo -V 15 | script: 16 | - cargo build -v 17 | - cargo doc 18 | after_script: 19 | - cd target && curl http://www.rust-ci.org/artifacts/put?t=$RUSTCI_TOKEN | sh 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sonorous 2 | 3 | [![Sonorous on Travis CI][travis-image]][travis] 4 | 5 | [travis-image]: https://travis-ci.org/snrs/sonorous.png 6 | [travis]: https://travis-ci.org/snrs/sonorous 7 | 8 | Sonorous is a music video game, originated from [Angolmois](http://mearie.org/projects/angolmois/), [Angolmois Rust Edition](https://github.com/lifthrasiir/angolmois-rust/) and in turn [theseit project](http://theseit.ruree.net/). Highly experimental at the moment. 9 | 10 | Please consult the [temporary page](http://cosmic.mearie.org/f/sonorous/) for Sonorous if you are interested in it. 11 | 12 | ## License 13 | 14 | Sonorous is licensed under the GNU GPL version 2 or later unless otherwise noted. See `LICENSE.txt` for full terms. 15 | 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sonorous" 3 | version = "0.1.0" 4 | authors = ["Kang Seonghoon "] 5 | build = "make rust-deps" 6 | 7 | description = "Sonorous, an experimental music video game" 8 | homepage = "https://github.com/snrs/sonorous/" 9 | license = "GPL-2.0+" 10 | 11 | [[bin]] 12 | name = "sonorous" 13 | 14 | [dependencies.sdl] 15 | git = "https://github.com/lifthrasiir/rust-sdl.git" 16 | 17 | [dependencies.sdl_image] 18 | git = "https://github.com/lifthrasiir/rust-sdl.git" 19 | 20 | [dependencies.sdl_mixer] 21 | git = "https://github.com/lifthrasiir/rust-sdl.git" 22 | 23 | [dependencies.encoding] 24 | git = "https://github.com/lifthrasiir/rust-encoding.git" 25 | 26 | [dependencies.opengles] 27 | git = "https://github.com/lifthrasiir/rust-opengles-angle.git" 28 | 29 | [dependencies.sqlite3] 30 | git = "https://github.com/lifthrasiir/rustsqlite.git" 31 | 32 | [dependencies.cson] 33 | git = "https://github.com/lifthrasiir/cson-rust.git" 34 | 35 | [dependencies.encoding-index-korean] 36 | git = "https://github.com/lifthrasiir/rust-encoding.git" 37 | 38 | [dependencies.encoding-index-japanese] 39 | git = "https://github.com/lifthrasiir/rust-encoding.git" 40 | -------------------------------------------------------------------------------- /src/util/envelope.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md for details. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or 6 | // the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! Envelope for sending a non-sendable but effectively owned type across tasks. 10 | 11 | use std::mem; 12 | 13 | /// A wrapper to send a non-sendable owned type across tasks. Use with care. 14 | pub struct Envelope { 15 | wrapped: uint 16 | } 17 | 18 | impl Envelope { 19 | /// Creates a sendable wrapper out of the owned value. 20 | pub fn new(res: T) -> Envelope { 21 | let res: Box = box res; 22 | unsafe { 23 | Envelope { wrapped: mem::transmute(res) } 24 | } 25 | } 26 | 27 | /// Converts a sendable wrapper to the owned value. 28 | pub fn unwrap(self) -> T { 29 | unsafe { 30 | let ret: Box = mem::transmute(self.wrapped); 31 | mem::forget(self); 32 | *ret 33 | } 34 | } 35 | } 36 | 37 | #[unsafe_destructor] 38 | impl Drop for Envelope { 39 | fn drop(&mut self) { 40 | unsafe { 41 | let _: Box = mem::transmute(self.wrapped); 42 | } 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/ext/sdl.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md for details. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or 6 | // the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! Extensions to rust-sdl. 10 | 11 | #[cfg(target_os = "windows")] 12 | pub mod syswm { 13 | use libc::{HANDLE, INVALID_HANDLE_VALUE}; 14 | 15 | pub mod ll { 16 | #![allow(non_camel_case_types)] 17 | 18 | use libc::{HANDLE, c_int}; 19 | 20 | #[repr(C)] 21 | pub struct SDL_version { 22 | pub major: u8, 23 | pub minor: u8, 24 | pub patch: u8 25 | } 26 | 27 | impl SDL_version { 28 | pub fn new() -> SDL_version { 29 | SDL_version { major: 1, minor: 2, patch: 14 } 30 | } 31 | } 32 | 33 | #[repr(C)] 34 | pub struct SDL_SysWMinfo { 35 | pub version: SDL_version, 36 | pub window: HANDLE, 37 | pub hglrc: HANDLE 38 | } 39 | 40 | extern { 41 | pub fn SDL_GetWMInfo(info: *mut SDL_SysWMinfo) -> c_int; 42 | } 43 | } 44 | 45 | pub fn get_wm_info() -> Option { 46 | let mut wminfo = ll::SDL_SysWMinfo { 47 | version: ll::SDL_version::new(), 48 | window: INVALID_HANDLE_VALUE as HANDLE, 49 | hglrc: INVALID_HANDLE_VALUE as HANDLE, 50 | }; 51 | unsafe { 52 | if ll::SDL_GetWMInfo(&mut wminfo) == 0 { 53 | None 54 | } else { 55 | Some(wminfo) 56 | } 57 | } 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/gfx/color.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md for details. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or 6 | // the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! Utilities for SDL colors. 10 | 11 | pub use sdl::video::{Color, RGB, RGBA}; 12 | 13 | /// Extracts red, green, blue components from given color. 14 | pub fn to_rgb(c: Color) -> (u8, u8, u8) { 15 | match c { RGB(r, g, b) | RGBA(r, g, b, _) => (r, g, b) } 16 | } 17 | 18 | /// Extracts red, green, blue and alpha components from given color. A color without alpha is 19 | /// assumed to be totally opaque. 20 | pub fn to_rgba(c: Color) -> (u8, u8, u8, u8) { 21 | match c { RGB(r, g, b) => (r, g, b, 255), RGBA(r, g, b, a) => (r, g, b, a) } 22 | } 23 | 24 | /// Linear color gradient. 25 | #[deriving(PartialEq)] 26 | pub struct Gradient { 27 | /// A color at the position 0.0. Normally used as a topmost value. 28 | pub zero: Color, 29 | /// A color at the position 1.0. Normally used as a bottommost value. 30 | pub one: Color 31 | } 32 | 33 | /// A trait for color or color gradient. The color at the particular position can be calculated 34 | /// with `blend` method. 35 | pub trait Blend { 36 | /// Returns itself. This is same as `Clone::clone` but redefined here due to the inability 37 | /// of implementing `Clone` for `Color`. 38 | fn clone(&self) -> Self; 39 | /// Calculates the color at the position `num/denom`. 40 | fn blend(&self, num: int, denom: int) -> Color; 41 | } 42 | 43 | impl Blend for Color { 44 | fn clone(&self) -> Color { *self } 45 | fn blend(&self, _num: int, _denom: int) -> Color { *self } 46 | } 47 | 48 | impl Blend for Gradient { 49 | fn clone(&self) -> Gradient { *self } 50 | fn blend(&self, num: int, denom: int) -> Color { 51 | fn mix(x: u8, y: u8, num: int, denom: int) -> u8 { 52 | let x = x as int; 53 | let y = y as int; 54 | (y + ((x - y) * num / denom)) as u8 55 | } 56 | 57 | let (r0, g0, b0) = to_rgb(self.zero); 58 | let (r1, g1, b1) = to_rgb(self.one); 59 | RGB(mix(r1, r0, num, denom), mix(g1, g0, num, denom), mix(b1, b0, num, denom)) 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /res/skin/loading.cson: -------------------------------------------------------------------------------- 1 | # This is a part of Sonorous. 2 | # Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | # See README.md and LICENSE.txt for details. 4 | 5 | nodes: [ 6 | # this would be overwritten when there is a stagefile 7 | {$text: "loading bms file..." 8 | at = ["50%","50%"] 9 | size = 32 10 | color = ["#808080","#202020"] 11 | anchor = [0.5,0.5]} 12 | 13 | {$$: "meta.stagefile", $then: [ 14 | {$rect: "meta.stagefile" 15 | at = [[0,0],["100%","100%"]]} 16 | ]} 17 | 18 | {$$: "opts.showinfo", $then: [ 19 | # top 20 | {$rect: null 21 | at = [[0,0],["100%",42]] 22 | color = "#101010c0"} 23 | {$text: {$: "meta.title"} 24 | at = [6,4] 25 | size = 32 26 | color = ["white","gray"]} 27 | {$text: {$: "meta.genre"} 28 | at = ["100%-8",4] 29 | size = 16 30 | color = ["white","gray"] 31 | anchor = "right"} 32 | {$text: {$: "meta.artist"} 33 | at = ["100%-8",20] 34 | size = 16 35 | color = ["white","gray"] 36 | anchor = "right"} 37 | 38 | # bottom 39 | {$rect: null 40 | at = [[0,"100%-20"],["100%","100%"]] 41 | color = "#101010c0"} 42 | {$text: ["Level ", {$: "meta.level"}, 43 | " | BPM ", {$: "timeline.initbpm", format = "..0.00"}, 44 | {$$: "timeline.bpmchange", $then: "?"}, 45 | " | ", {$: "timeline.nnotes"}, 46 | {$$text: "timeline.nnotes", "1": " note", $default: " notes"}, 47 | " [", {$: "meta.nkeys"}, "KEY", 48 | {$$: "timeline.longnote", $then: "-LN"}, 49 | {$$: "meta.difficulty", 50 | "beginner": " BEGINNER", "normal": " NORMAL", "hard": " HARD", 51 | "extra": " EXTRA", "insane": " INSANE"}, "]"] 52 | at = [3,"100%-18"] 53 | size = 16 54 | color = ["white","gray"]} 55 | {$text: {$$: "loading", 56 | $then: [{$: "loading.path"}, " (", 57 | {$: "loading.ratio", format = "..0 * 100"}, "%)"], 58 | $else: "loading..."}, 59 | at = ["100%-3","100%-18"] 60 | size = 16 61 | color = ["silver","gray"] 62 | anchor = "right"} 63 | ]} 64 | ] 65 | 66 | # vim: syn=javascript ts=4 sts=4 sw=4 et ai 67 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # This is a part of Sonorous. 2 | # Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | # See README.md and LICENSE.txt for details. 4 | 5 | TARGET ?= target 6 | TARGETREL ?= target/release 7 | 8 | RUSTOPENGLESANGLE ?= libs/rust-opengles 9 | DIRECTX_SDK_INCLUDES ?= libs/w32api-directx-standalone/include 10 | SQLITE3 ?= libs/sqlite3 11 | 12 | GIT ?= git 13 | DLLTOOL ?= dlltool 14 | CFLAGS ?= -Os 15 | CXXFLAGS ?= -Os 16 | 17 | .PHONY: all 18 | all: 19 | @echo 'Use Cargo to build Sonorous.' 20 | @echo 'This Makefile is used for external dependencies. In particular, use `$(MAKE) deps` for copying non-system dependencies into `target/` and `target/release/`.' 21 | 22 | .PHONY: update-git-submodule 23 | update-git-submodule: 24 | $(GIT) submodule init 25 | $(GIT) submodule update 26 | 27 | .PHONY: deps 28 | deps: \ 29 | update-git-submodule \ 30 | $(TARGET)/libGLESv2.dll \ 31 | $(TARGET)/libEGL.dll \ 32 | $(TARGETREL)/libGLESv2.dll \ 33 | $(TARGETREL)/libEGL.dll 34 | 35 | .PHONY: angle 36 | angle: 37 | cd $(RUSTOPENGLESANGLE) && $(MAKE) angle DIRECTX_SDK_INCLUDES=$(realpath $(DIRECTX_SDK_INCLUDES)) 38 | 39 | $(RUSTOPENGLESANGLE)/angle/src/libGLESv2.dll $(RUSTOPENGLESANGLE)/angle/src/libEGL.dll: angle 40 | $(TARGET)/libGLESv2.dll: $(RUSTOPENGLESANGLE)/angle/src/libGLESv2.dll 41 | mkdir -p $(dir $@) 42 | cp $< $@ 43 | $(TARGET)/libEGL.dll: $(RUSTOPENGLESANGLE)/angle/src/libEGL.dll 44 | mkdir -p $(dir $@) 45 | cp $< $@ 46 | $(TARGETREL)/libGLESv2.dll: $(RUSTOPENGLESANGLE)/angle/src/libGLESv2.dll 47 | mkdir -p $(dir $@) 48 | cp $< $@ 49 | $(TARGETREL)/libEGL.dll: $(RUSTOPENGLESANGLE)/angle/src/libEGL.dll 50 | mkdir -p $(dir $@) 51 | cp $< $@ 52 | 53 | .PHONY: rust-deps 54 | ifneq (,$(findstring MINGW,$(shell uname -s))) 55 | rust-deps: \ 56 | update-git-submodule \ 57 | $(OUT_DIR)/libsqlite3.a \ 58 | $(OUT_DIR)/libGLESv2.dll \ 59 | $(OUT_DIR)/libEGL.dll 60 | else 61 | rust-deps: 62 | endif 63 | 64 | $(SQLITE3)/libsqlite3.a: 65 | cd $(SQLITE3) && $(MAKE) all 66 | 67 | $(OUT_DIR)/libsqlite3.a: $(SQLITE3)/libsqlite3.a 68 | cp $< $@ 69 | # XXX technically we may be able to use .def files instead of .dll, but it currently doesn't work 70 | $(OUT_DIR)/libGLESv2.dll: $(RUSTOPENGLESANGLE)/angle/src/libGLESv2.dll 71 | cp $< $@ 72 | $(OUT_DIR)/libEGL.dll: $(RUSTOPENGLESANGLE)/angle/src/libEGL.dll 73 | cp $< $@ 74 | 75 | .PHONY: clean-all clean-angle clean-sqlite3 76 | clean-all: clean-angle clean-sqlite3 77 | clean-angle: 78 | cd $(RUSTOPENGLESANGLE) && $(MAKE) clean-all 79 | clean-sqlite3: 80 | cd $(SQLITE3) && $(MAKE) clean 81 | 82 | -------------------------------------------------------------------------------- /src/ui/playresult.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md and LICENSE.txt for details. 4 | 5 | //! Play result screen. Only used when the graphical `PlayingScene` finishes. 6 | 7 | use std::rc::Rc; 8 | use std::cell::RefCell; 9 | 10 | use sdl::event; 11 | use sdl::event::Event; 12 | use gfx::screen::Screen; 13 | use gfx::skin::render::Renderer; 14 | use engine::player::Player; 15 | use ui::scene::{Scene, SceneOptions, SceneCommand}; 16 | 17 | /// Play result scene. 18 | pub struct PlayResultScene { 19 | /// Display screen. 20 | pub screen: Rc>, 21 | /// Game play state after playing. 22 | pub player: Player, 23 | /// Skin renderer. 24 | pub skin: RefCell, 25 | } 26 | 27 | impl PlayResultScene { 28 | /// Creates a new play result scene from the game play state after `PlayingScene`. 29 | pub fn new(screen: Rc>, player: Player) -> Box { 30 | let skin = match player.opts.load_skin("playresult.cson") { 31 | Ok(skin) => skin, 32 | Err(err) => die!("{}", err), 33 | }; 34 | box PlayResultScene { screen: screen, player: player, 35 | skin: RefCell::new(Renderer::new(skin)) } 36 | } 37 | } 38 | 39 | impl Scene for PlayResultScene { 40 | fn activate(&mut self) -> SceneCommand { SceneCommand::Continue } 41 | 42 | fn scene_options(&self) -> SceneOptions { SceneOptions::new().tpslimit(20).fpslimit(1) } 43 | 44 | fn tick(&mut self) -> SceneCommand { 45 | loop { 46 | match event::poll_event() { 47 | Event::Key(event::Key::Escape,true,_,_) | 48 | Event::Key(event::Key::Return,true,_,_) => { return SceneCommand::Pop; } 49 | Event::Quit => { return SceneCommand::Exit; } 50 | Event::None => { break; } 51 | _ => {} 52 | } 53 | } 54 | SceneCommand::Continue 55 | } 56 | 57 | fn render(&self) { 58 | let mut screen = self.screen.borrow_mut(); 59 | 60 | screen.clear(); 61 | self.skin.borrow_mut().render(screen.deref_mut(), self); 62 | screen.swap_buffers(); 63 | } 64 | 65 | fn deactivate(&mut self) {} 66 | 67 | fn consume(self: Box) -> Box { panic!("unreachable"); } 68 | } 69 | 70 | define_hooks! { 71 | for PlayResultScene |scene, id, parent, body| { 72 | delegate scene.player; 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /src/ui/init.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md and LICENSE.txt for details. 4 | 5 | //! Initialization. 6 | 7 | use sdl; 8 | use sdl_image; 9 | use sdl_mixer; 10 | use engine::resource::{BGAW, BGAH, SAMPLERATE}; 11 | use gfx::screen::Screen; 12 | 13 | /// The width of screen, unless the exclusive mode. 14 | pub const SCREENW: uint = 800; 15 | /// The height of screen, unless the exclusive mode. 16 | pub const SCREENH: uint = 600; 17 | 18 | /// Initializes SDL video subsystem, and creates a small screen for BGAs (`BGAW` by `BGAH` pixels) 19 | /// if `exclusive` is set, or a full-sized screen (`SCREENW` by `SCREENH` pixels) otherwise. 20 | /// `fullscreen` is ignored when `exclusive` is set. 21 | pub fn init_video(exclusive: bool, fullscreen: bool) -> Screen { 22 | if !sdl::init([sdl::InitFlag::Video][]) { 23 | die!("SDL Initialization Failure: {}", sdl::get_error()); 24 | } 25 | sdl_image::init([sdl_image::InitFlag::JPG, sdl_image::InitFlag::PNG][]); 26 | 27 | let (width, height, fullscreen) = if exclusive { 28 | (BGAW, BGAH, false) 29 | } else { 30 | (SCREENW, SCREENH, fullscreen) 31 | }; 32 | let screen = match Screen::new(width, height, fullscreen) { 33 | Ok(screen) => screen, 34 | Err(err) => die!("Failed to initialize screen: {}", err) 35 | }; 36 | if !exclusive { 37 | sdl::mouse::set_cursor_visible(false); 38 | } 39 | 40 | sdl::wm::set_caption(::version()[], ""); 41 | screen 42 | } 43 | 44 | /// Initializes SDL audio subsystem and SDL_mixer. 45 | pub fn init_audio() { 46 | if !sdl::init([sdl::InitFlag::Audio][]) { 47 | die!("SDL Initialization Failure: {}", sdl::get_error()); 48 | } 49 | //sdl_mixer::init([sdl_mixer::InitFlag::OGG, sdl_mixer::InitFlag::MP3][]); // TODO 50 | if sdl_mixer::open(SAMPLERATE, sdl::audio::S16_AUDIO_FORMAT, 51 | sdl::audio::Channels::Stereo, 2048).is_err() { 52 | die!("SDL Mixer Initialization Failure"); 53 | } 54 | } 55 | 56 | /// Initializes a joystick with given index. 57 | pub fn init_joystick(joyidx: uint) -> sdl::joy::Joystick { 58 | if !sdl::init([sdl::InitFlag::Joystick][]) { 59 | die!("SDL Initialization Failure: {}", sdl::get_error()); 60 | } 61 | unsafe { 62 | sdl::joy::ll::SDL_JoystickEventState(1); // TODO rust-sdl patch 63 | } 64 | match sdl::joy::Joystick::open(joyidx as int) { 65 | Ok(joy) => joy, 66 | Err(err) => die!("SDL Joystick Initialization Failure: {}", err) 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /res/skin/playresult.cson: -------------------------------------------------------------------------------- 1 | # This is a part of Sonorous. 2 | # Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | # See README.md and LICENSE.txt for details. 4 | 5 | nodes: [ 6 | {$rect: null 7 | at = [[0,20],["100%","120"]] 8 | color = "white"} 9 | {$text: {$$: "player.survival", $then: "CLEARED!", $else: "FAILED..."} 10 | at = ["50%","38"] 11 | size = 64 12 | anchor = "center" 13 | color = "black"} 14 | 15 | # grade counts 16 | [ 17 | {$clip: [[0,160],["100%",270]]} 18 | {$$: "player.grades", $then: [ 19 | {$text: {$$text: "grade.name", "miss": "MISS", "bad": "BAD", "good": "GOOD", 20 | "great": "GREAT", "cool": "COOL"} 21 | at = [25,0] 22 | size = 32 23 | color = {$$text: "grade.name", "miss": ["#ffc0c0", "#ff4040"], 24 | "bad": ["#ffc0ff", "#ff40ff"], 25 | "good": ["#ffffc0", "#ffff40"], 26 | "great": ["#c0ffc0", "#40ff40"], 27 | "cool": ["#c0c0ff", "#4040ff"]}} 28 | {$text: {$: "grade.count", format = "0000"} 29 | at = [25,35] 30 | size = 48 31 | color = "white" 32 | zerocolor = "gray"} 33 | 34 | # move the clipping rect by 20%-5, but don't shrink the width itself 35 | {$clip: [["20%-5",0],["120%-5","100%"]]} 36 | ]} 37 | ] 38 | 39 | # score 40 | {$text: "SCORE" 41 | at = [25,270] 42 | size = 32 43 | color = "white"} 44 | {$text: [{$: "player.score", format = "0000000"}, "/"], 45 | at = [25,305] 46 | size = 48 47 | color = "white" 48 | zerocolor = "gray"} 49 | {$text: {$: "timeline.maxscore", format = "0000000"}, 50 | at = [217,305] 51 | size = 48 52 | color = "white" 53 | zerocolor = "gray"} 54 | 55 | # combos 56 | {$text: "MAX COMBO" 57 | at = ["60%+10",270] 58 | size = 32 59 | color = "white"} 60 | {$text: [{$: "player.bestcombo", format = "0000"}, "/"] 61 | at = ["60%+10",305] 62 | size = 48 63 | color = "white" 64 | zerocolor = "gray"} 65 | {$text: {$: "timeline.nnotes", format = "0000"} 66 | at = ["60%+130",305] 67 | size = 48 68 | color = "white" 69 | zerocolor = "gray"} 70 | 71 | {$text: "Press Return key to continue." 72 | at = ["50%","100%-40"] 73 | size = 16 74 | anchor = "center" 75 | color = "white"} 76 | ] 77 | 78 | # vim: syn=javascript ts=4 sts=4 sw=4 et ai 79 | -------------------------------------------------------------------------------- /src/format/metadata.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md and LICENSE.txt for details. 4 | 5 | //! Common metadata. 6 | 7 | /// A numerical rating of the chart specified by the author. 8 | /// 9 | /// Due to the long history of coexisting different rating systems (BMS, for example, has 10 | /// at least five of them), this is a mostly freeform number associated with the rating system. 11 | /// Levels in different rating systems are incompatible to others. 12 | #[deriving(Clone, PartialEq)] 13 | pub struct Level { 14 | /// The numeric rating. 15 | pub value: int, 16 | /// The rating system. 17 | pub system: LevelSystem, 18 | } 19 | 20 | /// A unique identifier for the rating system. 21 | #[deriving(PartialEq, Eq, FromPrimitive, Clone)] 22 | pub enum LevelSystem { 23 | // FIXME this is a temporary, ambiguous rating system used by BMS 24 | Bms = 1, 25 | } 26 | 27 | /// Difficulty group specified by the author. 28 | /// 29 | /// Does not affect the actual game play but affects the selection screen 30 | /// by grouping related charts. 31 | #[deriving(PartialEq, Eq, PartialOrd, Ord, Clone)] 32 | pub struct Difficulty(pub int); 33 | 34 | impl Difficulty { 35 | /// Returns a string for representing the difficulty if any. This is used only for convenience. 36 | pub fn name(&self) -> Option<&'static str> { 37 | // this set of strings is designed to be unique in the first character and compatible to 38 | // existing subtitle detection rules in other implementations. 39 | match *self { 40 | Difficulty(1) => Some("BEGINNER"), 41 | Difficulty(2) => Some("NORMAL"), 42 | Difficulty(3) => Some("HARD"), 43 | Difficulty(4) => Some("EXTRA"), 44 | Difficulty(5) => Some("INSANE"), 45 | _ => None, 46 | } 47 | } 48 | } 49 | 50 | /// Common metadata for music data formats. 51 | #[deriving(Clone)] 52 | pub struct Meta { 53 | /// True when the metadata is subject to the randomness. 54 | /// This distinction is important since this would mean that we can't easily cache 55 | /// the metadata for listing purpose. 56 | pub random: bool, 57 | 58 | /// Title. 59 | pub title: Option, 60 | /// Subtitle(s). Intended to be rendered in small print or only in certain cases. 61 | pub subtitles: Vec, 62 | /// Purported genre. Many such "genres" are made up and may not match the actual genre. 63 | pub genre: Option, 64 | /// Purported artist description. Normally includes composer, arranger, remixer and/or singer. 65 | /// Again, many such "artists" may not match the actual artist. 66 | pub artist: Option, 67 | /// Secondary artist(s). Normally includes game data creator, BGA designer and so on. 68 | pub subartists: Vec, 69 | /// Comment(s). 70 | pub comments: Vec, 71 | 72 | /// The numerical chart rating specified by the author. Does not affect the actual game play. 73 | pub level: Option, 74 | /// Difficulty group specified by the author. 75 | pub difficulty: Option, 76 | } 77 | 78 | -------------------------------------------------------------------------------- /src/util/std.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md for details. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or 6 | // the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! Additions to the standard library. 10 | 11 | /// String utilities for Rust. Parallels to `std::str`. 12 | pub mod str { 13 | /// Extensions to `str`. 14 | pub trait StrUtil<'r> { 15 | /// Returns a slice of the given string starting from `begin` and up to the byte 16 | /// position `end`. `end` doesn't have to point to valid characters. 17 | /// 18 | /// # Failure 19 | /// 20 | /// If `begin` does not point to valid characters or beyond the last character of 21 | /// the string, or `end` points beyond the last character of the string 22 | fn slice_upto(&self, begin: uint, end: uint) -> &'r str; 23 | 24 | /// Counts the number of bytes in the complete UTF-8 sequences up to `limit` bytes 25 | /// in `s` starting from `start`. 26 | fn count_bytes_upto(&self, start: uint, limit: uint) -> uint; 27 | 28 | /// Work with a null-terminated UTF-16 buffer of the string. Useful for calling 29 | /// Win32 API. 30 | fn as_utf16_c_str(&self, f: |*const u16| -> T) -> T; 31 | } 32 | 33 | impl<'r> StrUtil<'r> for &'r str { 34 | fn slice_upto(&self, begin: uint, end: uint) -> &'r str { 35 | (*self)[begin..begin + self.count_bytes_upto(begin, end)] 36 | } 37 | 38 | fn count_bytes_upto(&self, start: uint, limit: uint) -> uint { 39 | assert!(self.is_char_boundary(start)); 40 | let limit = start + limit; 41 | let l = self.len(); 42 | assert!(limit < l); 43 | let mut end = start; 44 | loop { 45 | assert!(end < l); 46 | let next = self.char_range_at(end).next; 47 | if next > limit { break; } 48 | end = next; 49 | } 50 | end - start 51 | } 52 | 53 | fn as_utf16_c_str(&self, f: |*const u16| -> T) -> T { 54 | let mut s16: Vec = self.utf16_units().collect(); 55 | s16.push(0u16); 56 | f(s16.as_ptr()) 57 | } 58 | } 59 | } 60 | 61 | /// Option utilities for Rust. Parallels to `std::option`. 62 | pub mod option { 63 | /// An utility trait for an option of string or alikes. 64 | pub trait StrOption { 65 | /// Returns a string slice in the option if any. 66 | fn as_ref_slice<'a>(&'a self) -> Option<&'a str>; 67 | 68 | /// Returns a string slice in the option if any, or `default` otherwise. 69 | fn as_ref_slice_or<'a>(&'a self, default: &'a str) -> &'a str { 70 | self.as_ref_slice().unwrap_or(default) 71 | } 72 | } 73 | 74 | impl StrOption for Option { 75 | fn as_ref_slice<'a>(&'a self) -> Option<&'a str> { 76 | self.as_ref().map(|s| s.as_slice()) 77 | } 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /src/util/console.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md for details. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or 6 | // the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! Encoding-aware console I/O. 10 | 11 | use std::cell::Cell; 12 | use std::io::{stdout, stderr}; 13 | use encoding::{Encoding, EncodingRef, Encoder, EncoderTrap, ByteWriter}; 14 | 15 | thread_local!(static CONSOLE_ENCODING: Cell> = Cell::new(None)) 16 | 17 | /// Returns an encoding usable for console I/O. 18 | #[cfg(target_os = "windows")] 19 | #[allow(experimental)] 20 | fn get_console_encoding() -> EncodingRef { 21 | use ext::win32::ll::GetACP; 22 | use encoding::all::ASCII; 23 | use encoding::label::encoding_from_windows_code_page; 24 | let cp = unsafe { GetACP() } as uint; 25 | encoding_from_windows_code_page(cp).unwrap_or(ASCII as EncodingRef) 26 | } 27 | 28 | /// Returns an encoding usable for console I/O. 29 | #[cfg(not(target_os = "windows"))] 30 | fn get_console_encoding() -> EncodingRef { 31 | use encoding::all::UTF_8; 32 | UTF_8 as EncodingRef // TODO 33 | } 34 | 35 | /// Returns an encoding usable for console I/O. 36 | /// The result is cached to the task-local storage. 37 | pub fn console_encoding() -> EncodingRef { 38 | CONSOLE_ENCODING.with(|enc| { 39 | match enc.get() { 40 | Some(encoding) => encoding, 41 | None => { 42 | let encoding = get_console_encoding(); 43 | enc.set(Some(encoding)); 44 | encoding 45 | } 46 | } 47 | }) 48 | } 49 | 50 | /// An encoder trap function used for `to_console_encoding`. 51 | fn hex_ncr_escape(_encoder: &mut Encoder, input: &str, output: &mut ByteWriter) -> bool { 52 | let mut escapes = String::new(); 53 | for ch in input.chars() { 54 | escapes.push_str(format!("&#x{:X};", ch as int)[]); 55 | } 56 | output.write_bytes(escapes.as_bytes()); 57 | true 58 | } 59 | 60 | /// Converts the string to the current console encoding. 61 | pub fn to_console_encoding(s: &str) -> Vec { 62 | console_encoding().encode(s, EncoderTrap::Call(hex_ncr_escape)).unwrap() 63 | } 64 | 65 | /// Same as `std::io::print` but converts to the current console encoding if possible. 66 | pub fn printout(s: &str) { 67 | let _ = stdout().write(to_console_encoding(s)[]); 68 | } 69 | 70 | /// Same as `std::io::println` but converts to the current console encoding if possible. 71 | pub fn printoutln(s: &str) { 72 | let mut out = stdout(); 73 | let _ = out.write(to_console_encoding(s)[]); 74 | let _ = out.write(['\n' as u8][]); 75 | } 76 | 77 | /// Same as `std::io::stderr().write_str` but converts to the current console encoding if possible. 78 | pub fn printerr(s: &str) { 79 | let _ = stderr().write(to_console_encoding(s)[]); 80 | } 81 | 82 | /// Same as `std::io::stderr().write_line` but converts to the current console encoding if possible. 83 | pub fn printerrln(s: &str) { 84 | let mut err = stderr(); 85 | let _ = err.write(to_console_encoding(s)[]); 86 | let _ = err.write(['\n' as u8][]); 87 | } 88 | 89 | -------------------------------------------------------------------------------- /src/ext/win32.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md for details. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or 6 | // the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! Win32 API wrappers. 10 | 11 | #![cfg(target_os = "windows")] 12 | 13 | pub mod ll { 14 | #![allow(non_camel_case_types)] 15 | 16 | use libc::{c_int, c_uint, c_void}; 17 | use libc::types::os::arch::extra::{BOOL, CHAR, WORD, DWORD, HANDLE}; 18 | use libc::types::os::arch::extra::{LPCSTR, LPWSTR, LPCWSTR}; 19 | 20 | pub type HWND = HANDLE; 21 | pub type HDC = HANDLE; 22 | pub type HINSTANCE = HANDLE; 23 | 24 | pub const OFN_HIDEREADONLY: DWORD = 4; 25 | 26 | #[allow(non_snake_case)] 27 | #[repr(C)] 28 | pub struct OPENFILENAMEW { 29 | pub lStructSize: DWORD, 30 | pub hwndOwner: HWND, 31 | pub hInstance: HINSTANCE, 32 | pub lpstrFilter: LPCWSTR, 33 | pub lpstrCustomFilter: LPWSTR, 34 | pub nMaxCustFilter: DWORD, 35 | pub nFilterIndex: DWORD, 36 | pub lpstrFile: LPWSTR, 37 | pub nMaxFile: DWORD, 38 | pub lpstrFileTitle: LPWSTR, 39 | pub nMaxFileTitle: DWORD, 40 | pub lpstrInitialDir: LPCWSTR, 41 | pub lpstrTitle: LPCWSTR, 42 | pub Flags: DWORD, 43 | pub nFileOffset: WORD, 44 | pub nFileExtension: WORD, 45 | pub lpstrDefExt: LPCWSTR, 46 | pub lCustData: DWORD, 47 | pub lpfnHook: *mut (), // XXX LPOFNHOOKPROC = fn(HWND,c_uint,WPARAM,LPARAM)->c_uint 48 | pub lpTemplateName: LPCWSTR, 49 | pub pvReserved: *mut c_void, 50 | pub dwReserved: DWORD, 51 | pub FlagsEx: DWORD, 52 | } 53 | 54 | #[allow(non_snake_case)] 55 | #[repr(C)] 56 | pub struct FILETIME { 57 | pub dwLowDateTime: DWORD, 58 | pub dwHighDateTime: DWORD, 59 | } 60 | 61 | #[allow(non_snake_case)] 62 | #[repr(C)] 63 | pub struct WIN32_FIND_DATAA { 64 | pub dwFileAttributes: DWORD, 65 | pub ftCreationTime: FILETIME, 66 | pub ftLastAccessTime: FILETIME, 67 | pub ftLastWriteTime: FILETIME, 68 | pub nFileSizeHigh: DWORD, 69 | pub nFileSizeLow: DWORD, 70 | pub dwReserved0: DWORD, 71 | pub dwReserved1: DWORD, 72 | pub cFileName: [CHAR, ..260], 73 | } 74 | 75 | #[link(name = "kernel32")] 76 | extern "stdcall" { 77 | pub fn FindFirstFileA(lpFileName: LPCSTR, 78 | lpFindFileData: *mut WIN32_FIND_DATAA) -> HANDLE; 79 | pub fn FindNextFileA(hFindFile: HANDLE, lpFindFileData: *mut WIN32_FIND_DATAA) -> BOOL; 80 | pub fn FindClose(hFindFile: HANDLE) -> BOOL; 81 | pub fn LoadLibraryA(lpFileName: LPCSTR) -> HANDLE; 82 | pub fn GetACP() -> c_uint; 83 | } 84 | 85 | #[link(name = "user32")] 86 | extern "stdcall" { 87 | pub fn MessageBoxW(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, 88 | uType: c_uint) -> c_int; 89 | pub fn GetDC(hwnd: HWND) -> HDC; 90 | } 91 | 92 | #[link(name = "comdlg32")] 93 | extern "stdcall" { 94 | pub fn GetOpenFileNameW(lpofn: *mut OPENFILENAMEW) -> BOOL; 95 | } 96 | } 97 | 98 | -------------------------------------------------------------------------------- /src/util/maybe_owned.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md for details. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or 6 | // the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! Optionally owned containers, similar to `std::str::MaybeOwned`. 10 | 11 | use std::{fmt, hash}; 12 | use std::clone::Clone; 13 | use std::cmp::{PartialEq, Eq, PartialOrd, Ord, Equiv}; 14 | use std::cmp::Ordering; 15 | use std::default::Default; 16 | 17 | /// A vector that can hold either `&'r [T]` or `Vec`. 18 | pub enum MaybeOwnedVec<'r, T:'r> { 19 | Owned(Vec), 20 | Slice(&'r [T]), 21 | } 22 | 23 | impl<'r,T> MaybeOwnedVec<'r,T> { 24 | /// Returns `true` if the vector is owned. 25 | #[inline] 26 | pub fn is_owned(&self) -> bool { 27 | match *self { 28 | MaybeOwnedVec::Owned(..) => true, 29 | MaybeOwnedVec::Slice(..) => false, 30 | } 31 | } 32 | 33 | /// Returns `true` if the vector is borrowed. 34 | #[inline] 35 | pub fn is_slice(&self) -> bool { 36 | match *self { 37 | MaybeOwnedVec::Owned(..) => false, 38 | MaybeOwnedVec::Slice(..) => true, 39 | } 40 | } 41 | 42 | /// Returns the length of vector. 43 | #[inline] 44 | pub fn len(&self) -> uint { self.as_slice().len() } 45 | } 46 | 47 | /// A trait for moving into an `MaybeOwnedVec`. 48 | pub trait IntoMaybeOwnedVec<'r,T> { 49 | /// Moves `self` into an `MaybeOwnedVec`. 50 | fn into_maybe_owned_vec(self) -> MaybeOwnedVec<'r,T>; 51 | } 52 | 53 | impl IntoMaybeOwnedVec<'static,T> for Vec { 54 | #[inline] 55 | fn into_maybe_owned_vec(self) -> MaybeOwnedVec<'static,T> { MaybeOwnedVec::Owned(self) } 56 | } 57 | 58 | impl<'r,T> IntoMaybeOwnedVec<'r,T> for &'r [T] { 59 | #[inline] 60 | fn into_maybe_owned_vec(self) -> MaybeOwnedVec<'r,T> { MaybeOwnedVec::Slice(self) } 61 | } 62 | 63 | impl<'r,T> AsSlice for MaybeOwnedVec<'r,T> { 64 | #[inline] 65 | fn as_slice<'r>(&'r self) -> &'r [T] { 66 | match *self { 67 | MaybeOwnedVec::Owned(ref v) => v[], 68 | MaybeOwnedVec::Slice(v) => v, 69 | } 70 | } 71 | } 72 | 73 | impl<'r,T:fmt::Show> fmt::Show for MaybeOwnedVec<'r,T> { 74 | #[inline] 75 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.as_slice().fmt(f) } 76 | } 77 | 78 | impl<'r,T:PartialEq> PartialEq for MaybeOwnedVec<'r,T> { 79 | #[inline] 80 | fn eq(&self, other: &MaybeOwnedVec<'r,T>) -> bool { 81 | self.as_slice().eq(other.as_slice()) 82 | } 83 | } 84 | 85 | impl<'r,T:Eq> Eq for MaybeOwnedVec<'r,T> {} 86 | 87 | impl<'r,T:PartialEq,V:AsSlice> Equiv for MaybeOwnedVec<'r,T> { 88 | #[inline] 89 | fn equiv(&self, other: &V) -> bool { 90 | self.as_slice().eq(other.as_slice()) 91 | } 92 | } 93 | 94 | impl<'r,T:PartialOrd> PartialOrd for MaybeOwnedVec<'r,T> { 95 | #[inline] 96 | fn partial_cmp(&self, other: &MaybeOwnedVec<'r,T>) -> Option { 97 | self.as_slice().partial_cmp(other.as_slice()) 98 | } 99 | } 100 | 101 | impl<'r,T:Ord> Ord for MaybeOwnedVec<'r,T> { 102 | #[inline] 103 | fn cmp(&self, other: &MaybeOwnedVec<'r,T>) -> Ordering { 104 | self.as_slice().cmp(other.as_slice()) 105 | } 106 | } 107 | 108 | impl<'r,T:Clone> Clone for MaybeOwnedVec<'r,T> { 109 | #[inline] 110 | fn clone(&self) -> MaybeOwnedVec<'r,T> { 111 | match *self { 112 | MaybeOwnedVec::Owned(ref v) => MaybeOwnedVec::Owned(v.clone()), 113 | MaybeOwnedVec::Slice(v) => MaybeOwnedVec::Slice(v), 114 | } 115 | } 116 | } 117 | 118 | impl Default for MaybeOwnedVec<'static,T> { 119 | #[inline] 120 | fn default() -> MaybeOwnedVec<'static,T> { MaybeOwnedVec::Slice(&[]) } 121 | } 122 | 123 | impl<'r,T:hash::Hash> hash::Hash for MaybeOwnedVec<'r,T> { 124 | #[inline] 125 | fn hash(&self, state: &mut hash::sip::SipState) { self.as_slice().hash(state) } 126 | } 127 | 128 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "sonorous" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "cson 0.1.3 (git+https://github.com/lifthrasiir/cson-rust.git)", 6 | "encoding 0.2.4 (git+https://github.com/lifthrasiir/rust-encoding.git)", 7 | "encoding-index-japanese 1.0.20140915 (git+https://github.com/lifthrasiir/rust-encoding.git)", 8 | "encoding-index-korean 1.0.20140915 (git+https://github.com/lifthrasiir/rust-encoding.git)", 9 | "opengles 0.1.0 (git+https://github.com/lifthrasiir/rust-opengles-angle.git)", 10 | "sdl 0.3.6 (git+https://github.com/lifthrasiir/rust-sdl.git)", 11 | "sdl_image 0.3.6 (git+https://github.com/lifthrasiir/rust-sdl.git)", 12 | "sdl_mixer 0.3.6 (git+https://github.com/lifthrasiir/rust-sdl.git)", 13 | "sqlite3 0.1.0 (git+https://github.com/lifthrasiir/rustsqlite.git)", 14 | ] 15 | 16 | [[package]] 17 | name = "cson" 18 | version = "0.1.3" 19 | source = "git+https://github.com/lifthrasiir/cson-rust.git#b8f1f7d51b9509a07a69c9d5f13f713203493d93" 20 | 21 | [[package]] 22 | name = "encoding" 23 | version = "0.2.4" 24 | source = "git+https://github.com/lifthrasiir/rust-encoding.git#46b4154264ad0c6034b29d36b250bed65f3c4328" 25 | dependencies = [ 26 | "encoding-index-japanese 1.0.20140915 (git+https://github.com/lifthrasiir/rust-encoding.git)", 27 | "encoding-index-korean 1.0.20140915 (git+https://github.com/lifthrasiir/rust-encoding.git)", 28 | "encoding-index-simpchinese 1.0.20140915 (git+https://github.com/lifthrasiir/rust-encoding.git)", 29 | "encoding-index-singlebyte 1.0.20140915 (git+https://github.com/lifthrasiir/rust-encoding.git)", 30 | "encoding-index-tradchinese 1.0.20140915 (git+https://github.com/lifthrasiir/rust-encoding.git)", 31 | ] 32 | 33 | [[package]] 34 | name = "encoding-index-japanese" 35 | version = "1.0.20140915" 36 | source = "git+https://github.com/lifthrasiir/rust-encoding.git#46b4154264ad0c6034b29d36b250bed65f3c4328" 37 | dependencies = [ 38 | "encoding_index_tests 0.1.0 (git+https://github.com/lifthrasiir/rust-encoding.git)", 39 | ] 40 | 41 | [[package]] 42 | name = "encoding-index-korean" 43 | version = "1.0.20140915" 44 | source = "git+https://github.com/lifthrasiir/rust-encoding.git#46b4154264ad0c6034b29d36b250bed65f3c4328" 45 | dependencies = [ 46 | "encoding_index_tests 0.1.0 (git+https://github.com/lifthrasiir/rust-encoding.git)", 47 | ] 48 | 49 | [[package]] 50 | name = "encoding-index-simpchinese" 51 | version = "1.0.20140915" 52 | source = "git+https://github.com/lifthrasiir/rust-encoding.git#46b4154264ad0c6034b29d36b250bed65f3c4328" 53 | dependencies = [ 54 | "encoding_index_tests 0.1.0 (git+https://github.com/lifthrasiir/rust-encoding.git)", 55 | ] 56 | 57 | [[package]] 58 | name = "encoding-index-singlebyte" 59 | version = "1.0.20140915" 60 | source = "git+https://github.com/lifthrasiir/rust-encoding.git#46b4154264ad0c6034b29d36b250bed65f3c4328" 61 | dependencies = [ 62 | "encoding_index_tests 0.1.0 (git+https://github.com/lifthrasiir/rust-encoding.git)", 63 | ] 64 | 65 | [[package]] 66 | name = "encoding-index-tradchinese" 67 | version = "1.0.20140915" 68 | source = "git+https://github.com/lifthrasiir/rust-encoding.git#46b4154264ad0c6034b29d36b250bed65f3c4328" 69 | dependencies = [ 70 | "encoding_index_tests 0.1.0 (git+https://github.com/lifthrasiir/rust-encoding.git)", 71 | ] 72 | 73 | [[package]] 74 | name = "encoding_index_tests" 75 | version = "0.1.0" 76 | source = "git+https://github.com/lifthrasiir/rust-encoding.git#46b4154264ad0c6034b29d36b250bed65f3c4328" 77 | 78 | [[package]] 79 | name = "opengles" 80 | version = "0.1.0" 81 | source = "git+https://github.com/lifthrasiir/rust-opengles-angle.git#92240a7a31c5dc1ce063046ec22386c0b7a1c74c" 82 | 83 | [[package]] 84 | name = "sdl" 85 | version = "0.3.6" 86 | source = "git+https://github.com/lifthrasiir/rust-sdl.git#fcf08194101ea31574e9b4a25d7c8c0a54c84e67" 87 | 88 | [[package]] 89 | name = "sdl_image" 90 | version = "0.3.6" 91 | source = "git+https://github.com/lifthrasiir/rust-sdl.git#fcf08194101ea31574e9b4a25d7c8c0a54c84e67" 92 | dependencies = [ 93 | "sdl 0.3.6 (git+https://github.com/lifthrasiir/rust-sdl.git)", 94 | ] 95 | 96 | [[package]] 97 | name = "sdl_mixer" 98 | version = "0.3.6" 99 | source = "git+https://github.com/lifthrasiir/rust-sdl.git#fcf08194101ea31574e9b4a25d7c8c0a54c84e67" 100 | dependencies = [ 101 | "sdl 0.3.6 (git+https://github.com/lifthrasiir/rust-sdl.git)", 102 | ] 103 | 104 | [[package]] 105 | name = "sqlite3" 106 | version = "0.1.0" 107 | source = "git+https://github.com/lifthrasiir/rustsqlite.git#50c3bedf1f7b56be4c2a09834a9d0f4fb7330373" 108 | 109 | -------------------------------------------------------------------------------- /src/util/filesearch.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md for details. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or 6 | // the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! Utilities for searching files. 10 | 11 | use std::{str, io}; 12 | use std::rc::Rc; 13 | use std::collections::{HashMap, hash_map}; 14 | use std::io::fs::PathExtensions; 15 | 16 | /// Context for searching files. 17 | pub struct SearchContext { 18 | /// Cached return values of `get_entries`. 19 | pub get_entries_cache: HashMap>,Rc>)>, 20 | } 21 | 22 | impl SearchContext { 23 | /// Creates a fresh search context. 24 | pub fn new() -> SearchContext { 25 | SearchContext { get_entries_cache: HashMap::new() } 26 | } 27 | 28 | /// Returns a list of immediate subdirectories (i.e. without `.` and `..`) and files 29 | /// in given directory. Returns a pair of empty lists if `dir` is not a directory. 30 | /// The results may be cached by the context. 31 | pub fn get_entries<'r>(&'r mut self, dir: &Path) -> (&'r [Path], &'r [Path]) { 32 | // the original plan is to implement an LRU cache for `get_entries`, but for now we don't 33 | // invalidate any cache items since it turned out that `os::list_dir` is very, very slow. 34 | // for example, it is not rare for `list_dir` to take more than 100ms in Windows. 35 | let &(ref dirs, ref files) = match self.get_entries_cache.entry(dir.clone()) { 36 | hash_map::Occupied(entries) => entries.into_mut(), 37 | hash_map::Vacant(entry) => { 38 | let entries = io::fs::readdir(dir).ok().unwrap_or_else(|| Vec::new()); 39 | let (dirs, files) = entries.partition(|path| path.is_dir()); 40 | entry.set((Rc::new(dirs), Rc::new(files))) 41 | } 42 | }; 43 | ((**dirs)[], (**files)[]) 44 | } 45 | 46 | /** 47 | * Resolves the specified resource path to the actual path if possible. May fail, but its 48 | * success doesn't guarantee that the resource should be read without a failure either. 49 | * 50 | * The actual resolution is complicated by the fact that many BMSes assume the case-insensitive 51 | * matching on file names and the coexistence between WAV resources and MP3 resources while 52 | * keeping the same BMS file. Therefore Sonorous adopted the following resolution rules: 53 | * 54 | * 1. Both `/` and `\` are accepted as a directory separator. 55 | * 2. Path components including file names are matched case-insensitively. If there are multiple 56 | * matches then any one can be used, even when a better match exists. 57 | * 3. If the initial match on the file name fails, and the file name does contain an extension, 58 | * then a list of alternative extensions is applied with the same matching procedure. 59 | */ 60 | pub fn resolve_relative_path(&mut self, basedir: &Path, path: &str, 61 | exts: &[&str]) -> Option { 62 | use std::ascii::AsciiExt; 63 | 64 | let mut parts = Vec::new(); 65 | for part in path.split(|c: char| c == '/' || c == '\\') { 66 | if part.is_empty() { continue; } 67 | parts.push(part); 68 | } 69 | if parts.is_empty() { return None; } 70 | 71 | let mut cur = basedir.clone(); 72 | let lastpart = parts.pop().unwrap(); 73 | for part in parts.iter() { 74 | let (dirs, _files) = self.get_entries(&cur); 75 | let part = part.to_ascii_upper(); 76 | let mut found = false; 77 | for next in dirs.iter() { 78 | let name = next.filename().and_then(str::from_utf8).map(|v| v.to_ascii_upper()); 79 | if name.as_ref().map_or(false, |name| *name == part) { 80 | cur = next.clone(); 81 | found = true; 82 | break; 83 | } 84 | } 85 | if !found { return None; } 86 | } 87 | 88 | let (_dirs, files) = self.get_entries(&cur); 89 | let lastpart = lastpart.to_ascii_upper(); 90 | for next in files.iter() { 91 | let name = next.filename().and_then(str::from_utf8).map(|v| v.to_ascii_upper()); 92 | let mut found = name.as_ref().map_or(false, |name| *name == lastpart); 93 | if !found && name.is_some() { 94 | let name = name.unwrap(); 95 | match name[].rfind('.') { 96 | Some(idx) => { 97 | let nextnoext = name[..idx]; 98 | for ext in exts.iter() { 99 | if nextnoext.to_string() + *ext == lastpart { 100 | found = true; 101 | break; 102 | } 103 | } 104 | } 105 | None => {} // does not try alternative extensions if there was no extension 106 | } 107 | } 108 | if found { 109 | return Some(next.clone()); 110 | } 111 | } 112 | 113 | None 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /src/ui/scene.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md and LICENSE.txt for details. 4 | 5 | //! Scene management. 6 | 7 | use std::io::timer::sleep; 8 | use std::time::Duration; 9 | use sdl::get_ticks; 10 | use ui::common::Ticker; 11 | 12 | /// Options used by the scene to customize the scene loop. 13 | #[deriving(Clone)] 14 | pub struct SceneOptions { 15 | /// If specified, limits the number of `Scene::tick` calls per second to this value. 16 | /// `run_scene` ensures this limitation by sleeping after each tick as needed. 17 | pub tpslimit: Option, 18 | /// If specified, limits the number of `Scene::render` calls per second to this value. 19 | /// Due to the implementation strategy `tpslimit` takes precedence over this if specified. 20 | pub fpslimit: Option, 21 | } 22 | 23 | impl SceneOptions { 24 | /// Creates default options for the scene. 25 | pub fn new() -> SceneOptions { 26 | SceneOptions { tpslimit: None, fpslimit: None } 27 | } 28 | 29 | /// Replaces `tpslimit` field with given value. 30 | pub fn tpslimit(self, tps: uint) -> SceneOptions { 31 | SceneOptions { tpslimit: Some(tps), ..self } 32 | } 33 | 34 | /// Replaces `fpslimit` field with given value. 35 | pub fn fpslimit(self, fps: uint) -> SceneOptions { 36 | SceneOptions { fpslimit: Some(fps), ..self } 37 | } 38 | } 39 | 40 | /// A command returned by `Scene`'s `tick` method. 41 | pub enum SceneCommand { 42 | /// Continues displaying this scene. 43 | Continue, 44 | /// Pushes a new `Scene` to the scene stack, making it the active scene. The current scene is 45 | /// stopped (after calling `deactivate`) until the new scene returns `PopScene` command. 46 | Push(Box), 47 | /// Replaces the current scene with a new `Scene` that will be returned by `consume` method. 48 | /// The command itself does not have a `Scene` argument since new scene may have to be 49 | /// constructured out of the existing scene. Therefore the scene should be prepared for 50 | /// multiple next scenes possible if any. 51 | Replace, 52 | /// Pops the current scene from the scene stack. The program exits if it was the only remaining 53 | /// scene in the stack. 54 | Pop, 55 | /// Clears the scene stack, effectively ending the program. 56 | Exit, 57 | } 58 | 59 | /// Scene interface. 60 | pub trait Scene { 61 | /// Called when the scene is to be activated, prior to the first `tick` call. May return 62 | /// a non-`Continue` command to immediately deactivate the scene. 63 | fn activate(&mut self) -> SceneCommand; 64 | 65 | /// Returns the options for this scene. It is called *after* the `activate` call. 66 | fn scene_options(&self) -> SceneOptions; 67 | 68 | /// Does the event handling and internal logics, and returns a command to instruct the caller. 69 | fn tick(&mut self) -> SceneCommand; 70 | 71 | /// Does the rendering jobs. It may get called once after the `tick` call (but not mandatory, 72 | /// for example, due to the frame drop). 73 | fn render(&self); 74 | 75 | /// Called when the scene is to be deactivated by the latest `tick` call. It is not called 76 | /// when `activate` returns a non-`Continue` command and the scene becomes deactivated. 77 | fn deactivate(&mut self); 78 | 79 | /// Called when the scene is to be replaced by a new `Scene` due to the `ReplaceScene` command. 80 | /// When called due to the `tick` call, this is called after `deactivate` call. 81 | fn consume(self: Box) -> Box; 82 | } 83 | 84 | /// Runs given scene and other additionally spawned scenes. 85 | pub fn run_scene(scene: Box) { 86 | let mut current = scene; 87 | let mut stack = Vec::new(); 88 | loop { 89 | let mut result = current.activate(); 90 | match result { 91 | SceneCommand::Continue => { 92 | let opts = current.scene_options(); 93 | let mintickdelay = opts.tpslimit.map_or(0, |tps| 1000 / tps); 94 | let interval = opts.fpslimit.map_or(0, |fps| 1000 / fps); 95 | let mut ticker = Ticker::with_interval(interval); 96 | loop { 97 | let ticklimit = get_ticks() + mintickdelay; 98 | result = current.tick(); 99 | match result { 100 | SceneCommand::Continue => { 101 | ticker.on_tick(get_ticks(), || { current.render(); }); 102 | } 103 | _ => { break; } 104 | } 105 | let now = get_ticks(); 106 | if now < ticklimit { sleep(Duration::milliseconds((ticklimit - now) as i64)); } 107 | } 108 | current.deactivate(); 109 | } 110 | _ => {} 111 | } 112 | match result { 113 | SceneCommand::Continue => { 114 | panic!("impossible"); 115 | } 116 | SceneCommand::Push(newscene) => { 117 | stack.push(current); 118 | current = newscene; 119 | } 120 | SceneCommand::Replace => { 121 | current = current.consume(); 122 | } 123 | SceneCommand::Pop => { 124 | if stack.is_empty() { break; } 125 | current = stack.pop().unwrap(); 126 | } 127 | SceneCommand::Exit => { 128 | break; 129 | } 130 | } 131 | } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /src/format/bms/encoding.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md and LICENSE.txt for details. 4 | 5 | //! Character encoding detection for BMS format. 6 | 7 | use std::cmp; 8 | use encoding::{Encoding, EncodingRef, DecoderTrap}; 9 | use encoding::all::{ASCII, UTF_8, WINDOWS_949, WINDOWS_31J}; 10 | 11 | use util::chardet::{Classifier, CharClassKo, CharClassJa, convert_raw_confidence}; 12 | 13 | static LOG_PROBS_KO: &'static [i32] = &[ 14 | 552483, -251065, -187207, -163086, -88603, -130451, -2906, -18512, -35744, -77761, 15 | -439522, -493587, -63872, 0, 0, 0, -447903, -450588, -192957, -424931, 16 | -428366, -439371, -381774, -437472, -464612, -440834, -430816, -412963, -443252, -455960, 17 | -439033, -465512, -481607, -452974, -295339, -394243, -417433, -436318, -424640, -453085, 18 | -408190, 0, -337120, -342559, -340045, -390810, -367378, -362360, -350409, -358721, 19 | -344365, -386048, -378418, -300543, -324063, -357782, -341811, -375471, -358808, -352643, 20 | -373660, -346715, -368680, -406217, -266907, -246069, -217794, -229556, -214754, -264366, 21 | -123848, -163253, -170876, -213532, -311807, -327884, -383674, -309190, -267631, -408071, 22 | -359482, -385177, -361882, -331612, -356844, -362450, -307037, -358975, -343735, -369816, 23 | -354754, -353362, -333292, -283308, 0, 476786, 464244, 551795, 158798, 154085, 24 | 148055, 157507, 423537, 474691, 465626, 470458, 480994, 483912, 483317, 472684, 25 | 486736, 467066, 464216, 494842, 442136, 456360, 458081, 462483, 384185, 186504, 26 | 188436, 207318, 206198, 202216, 266763, 202678, 151882, 153399, 150949, 149908, 27 | 150722, 148568, 149770, 150994, 151085, 145990, 149908, 151311, 151677, 149999, 28 | 151131, 150448, 149953, 150539, 149509, 140982, 152696, 150994, 151993, 151268, 29 | 148568, 151177, 144499, 151940, 151932, 150858, 146445, 151268, 150948, 149418, 30 | 151177, 151131, 149463, 151131, 150311, 150902, 150902, 31 | ]; 32 | 33 | static LOG_PROBS_JA: &'static [i32] = &[ 34 | 578168, 595533, -519562, -81564, -910, -463761, -551809, -129774, -58469, -2822, 35 | 0, 0, 0, 0, -25268, 0, 0, -423239, -428369, -452137, 36 | -433665, -413771, -435877, -437062, -426341, -459655, -469202, -412439, -466691, -428227, 37 | -427952, -439672, -435672, -462985, -396282, -455093, -430901, -417250, -460709, -457244, 38 | -406040, -396471, -409790, -427335, -425017, -434123, -419376, -421745, -389832, -145468, 39 | -119354, -123541, -69588, -178492, -145996, -139950, -155965, -114733, -116872, -122880, 40 | -248896, -136961, -134743, -151998, -24858, 289652, 33155, 270933, 159049, 290606, 41 | 29510, 248662, 94216, 256017, 141011, 299433, 64481, 236950, 150402, 275372, 42 | 116927, 228013, 92697, 312102, 131422, 106948, 0, 0, 0, 0, 43 | 245725, 378555, 243699, 354404, 0, 0, 0, 0, 0, 0, 44 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46 | 0, 0, 47 | ]; 48 | 49 | /// Reads the whole stream with given encoding. Any error would be substituted with U+FFFD. 50 | pub fn decode_stream(f: &mut Reader, encoding: EncodingRef) -> String { 51 | // TODO use incremental decoding when available 52 | let s = f.read_to_end().ok().unwrap_or_else(|| Vec::new()); 53 | encoding.decode(s[], DecoderTrap::Replace).unwrap() 54 | } 55 | 56 | /// Tries to guess the encoding of the stream and reads it accordingly. 57 | /// Any error would be substituted with U+FFFD. 58 | /// Returns a guessed encoding and confidence (probability) in addition to the decoded string. 59 | /// Currently recognizes `ASCII`, `UTF_8`, `WINDOWS_949` and `WINDOWS_31J` encodings. 60 | // 61 | // Rust: cannot change this to return `EncodingRef`, it seems to "infect" other uses of 62 | // `UTF_8.decode(...)` etc. to fail to resolve. 63 | pub fn guess_decode_stream(f: &mut Reader) -> (String, EncodingRef, f64) { 64 | let s: Vec = f.read_to_end().ok().unwrap_or_else(|| Vec::new()); 65 | 66 | // check for BOM (Sonorous proposal #1) 67 | if s.len() >= 3 && [0xef, 0xbb, 0xbf].equiv(&s[..3]) { 68 | return (UTF_8.decode(s[], DecoderTrap::Replace).unwrap(), UTF_8 as EncodingRef, 1.0); 69 | } 70 | 71 | // check for UTF-8 first line (Sonorous proposal #2) 72 | let first1k = s[..cmp::min(s.len(), 1024)]; 73 | let first1keol = first1k.iter().position(|&c| c == 0x0a).unwrap_or(first1k.len()); 74 | let firstline = first1k[..first1keol]; 75 | if firstline.iter().any(|&c| c >= 0x80) && 76 | UTF_8.decode(firstline, DecoderTrap::Strict).is_ok() { 77 | return (UTF_8.decode(s[], DecoderTrap::Replace).unwrap(), UTF_8 as EncodingRef, 1.0); 78 | } 79 | 80 | // ASCII: do we have to decode at all? 81 | if s.iter().all(|&c| c < 0x80) { 82 | return (ASCII.decode(s[], DecoderTrap::Replace).unwrap(), ASCII as EncodingRef, 1.0); 83 | } 84 | 85 | // Windows-949/31J: guess 86 | let ko = WINDOWS_949.decode(s[], DecoderTrap::Replace).unwrap(); 87 | let ja = WINDOWS_31J.decode(s[], DecoderTrap::Replace).unwrap(); 88 | let koconfidence = Classifier::new(CharClassKo, LOG_PROBS_KO).raw_confidence(ko[]); 89 | let jaconfidence = Classifier::new(CharClassJa, LOG_PROBS_JA).raw_confidence(ja[]); 90 | let (s, encoding, confidence) = 91 | if koconfidence < jaconfidence { 92 | (ko, WINDOWS_949 as EncodingRef, koconfidence) 93 | } else { 94 | (ja, WINDOWS_31J as EncodingRef, jaconfidence) 95 | }; 96 | (s, encoding, convert_raw_confidence(confidence)) 97 | } 98 | 99 | -------------------------------------------------------------------------------- /src/gfx/ratio_num.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md for details. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or 6 | // the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! A combined relative ("ratio") and absolute ("num") value. 10 | 11 | use std::fmt; 12 | use std::num::{Int, Float}; 13 | use std::default::Default; 14 | 15 | /// The sum of relative value (`ratio`) and absolute value (`num`), i.e. `ratio * base + num`. 16 | /// This is widely used for the specification and calculation of texture and window coordinates: 17 | /// `50%-30` would encode as `ratio` of 0.5 and `num` of -30 for example. 18 | #[deriving(Clone)] 19 | pub struct RatioNum { 20 | /// The relative component. 21 | pub ratio: T, 22 | /// The absolute component. 23 | pub num: T, 24 | } 25 | 26 | impl RatioNum { 27 | /// Returns a zero value. 28 | pub fn zero() -> RatioNum { 29 | RatioNum { ratio: Float::zero(), num: Float::zero() } 30 | } 31 | 32 | /// Returns a combined value out of the absolute value. 33 | pub fn from_num(num: T) -> RatioNum { 34 | RatioNum { ratio: Float::zero(), num: num } 35 | } 36 | 37 | /// Returns a combined value out of the relative value. 38 | pub fn from_ratio(ratio: T) -> RatioNum { 39 | RatioNum { ratio: ratio, num: Float::zero() } 40 | } 41 | } 42 | 43 | impl RatioNum { 44 | /// Returns `Some(self + rhs)` if it wouldn't occur overflow, or `None` otherwise. 45 | pub fn checked_add(&self, rhs: &RatioNum) -> Option> { 46 | let ratio = match self.ratio.checked_add(rhs.ratio) { Some(v) => v, None => return None }; 47 | let num = match self.num .checked_add(rhs.num ) { Some(v) => v, None => return None }; 48 | Some(RatioNum { ratio: ratio, num: num }) 49 | } 50 | 51 | /// Returns `Some(self - rhs)` if it wouldn't occur overflow, or `None` otherwise. 52 | pub fn checked_sub(&self, rhs: &RatioNum) -> Option> { 53 | let ratio = match self.ratio.checked_sub(rhs.ratio) { Some(v) => v, None => return None }; 54 | let num = match self.num .checked_sub(rhs.num ) { Some(v) => v, None => return None }; 55 | Some(RatioNum { ratio: ratio, num: num }) 56 | } 57 | } 58 | 59 | impl RatioNum { 60 | /// Returns a value equal to the reference base value. Note that this is not 61 | /// the true multiplicative identity, and `RatioNum` doesn't implement `One`. 62 | pub fn one() -> RatioNum { 63 | RatioNum { ratio: Float::one(), num: Float::zero() } 64 | } 65 | } 66 | 67 | impl+Mul> RatioNum { 68 | /// Given the base value to resolve the relative part, returns a calculated absolute value. 69 | pub fn to_num(&self, base: &T) -> T { 70 | self.ratio * *base + self.num 71 | } 72 | } 73 | 74 | impl+Div> RatioNum { 75 | /// Given the base value to resolve the relative part, returns a calculated relative value. 76 | pub fn to_ratio(&self, base: &T) -> T { 77 | self.ratio + self.num / *base 78 | } 79 | } 80 | 81 | impl+Mul> RatioNum { 82 | /// Substitutes the base value with another combined relative-absolute value: 83 | /// 84 | /// ```notrust 85 | /// | | 86 | /// |----|<------>|--------| self 87 | /// | | | | | 88 | /// |-----|<-------------------->|--| other 89 | /// | | | | 90 | /// |----------|<------>|-----------| self.subst(&other) 91 | /// | | 92 | /// ``` 93 | pub fn subst(&self, other: &RatioNum) -> RatioNum { 94 | // a (a' x + b') + b = (a a') x + (a b' + b) 95 | RatioNum { ratio: self.ratio * other.ratio, num: self.ratio * other.num + self.num } 96 | } 97 | } 98 | 99 | impl Default for RatioNum { 100 | fn default() -> RatioNum { 101 | RatioNum { ratio: Default::default(), num: Default::default() } 102 | } 103 | } 104 | 105 | impl, Result> Neg> for RatioNum { 106 | fn neg(&self) -> RatioNum { 107 | RatioNum { ratio: -self.ratio, num: -self.num } 108 | } 109 | } 110 | 111 | impl, RHS, Result> Add, RatioNum> for RatioNum { 112 | fn add(&self, rhs: &RatioNum) -> RatioNum { 113 | RatioNum { ratio: self.ratio + rhs.ratio, num: self.num + rhs.num } 114 | } 115 | } 116 | 117 | impl, RHS, Result> Sub, RatioNum> for RatioNum { 118 | fn sub(&self, rhs: &RatioNum) -> RatioNum { 119 | RatioNum { ratio: self.ratio - rhs.ratio, num: self.num - rhs.num } 120 | } 121 | } 122 | 123 | impl, RHS, Result> Mul> for RatioNum { 124 | fn mul(&self, rhs: &RHS) -> RatioNum { 125 | RatioNum { ratio: self.ratio * *rhs, num: self.num * *rhs } 126 | } 127 | } 128 | 129 | impl, RHS, Result> Div> for RatioNum { 130 | fn div(&self, rhs: &RHS) -> RatioNum { 131 | RatioNum { ratio: self.ratio / *rhs, num: self.num / *rhs } 132 | } 133 | } 134 | 135 | impl+FromPrimitive> fmt::Show for RatioNum { 136 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 137 | if self.ratio == Float::zero() { 138 | self.num.fmt(f) 139 | } else { 140 | let hundred = FromPrimitive::from_u64(100).unwrap(); 141 | if self.num == Float::zero() { 142 | write!(f, "{}%", self.ratio * hundred) 143 | } else { 144 | write!(f, "{}%{:+}", self.ratio * hundred, self.num) 145 | } 146 | } 147 | } 148 | } 149 | 150 | -------------------------------------------------------------------------------- /src/ui/common.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md and LICENSE.txt for details. 4 | 5 | //! Common UI patterns. 6 | 7 | #![macro_escape] 8 | 9 | use libc; 10 | use sdl::event; 11 | use util::console::{printerr, printerrln}; 12 | 13 | /// Immediately terminates the program with given exit code. 14 | pub fn exit(exitcode: int) -> ! { 15 | // Rust: `os::set_exit_status` doesn't immediately terminate the program. 16 | unsafe { libc::exit(exitcode as libc::c_int); } 17 | } 18 | 19 | /// Exits with an error message. Internally used in the `die!` macro below. 20 | #[cfg(target_os = "windows")] 21 | pub fn die(s: String) -> ! { 22 | use util::std::str::StrUtil; 23 | ::exename()[].as_utf16_c_str(|caption| { 24 | s[].as_utf16_c_str(|text| { 25 | unsafe { ::ext::win32::ll::MessageBoxW(::std::ptr::null_mut(), text, caption, 0); } 26 | }) 27 | }); 28 | exit(1) 29 | } 30 | 31 | /// Exits with an error message. Internally used in the `die!` macro below. 32 | #[cfg(not(target_os = "windows"))] 33 | pub fn die(s: String) -> ! { 34 | printerrln(format!("{}: {}", ::exename(), s)[]); 35 | exit(1) 36 | } 37 | 38 | /// Prints an warning message. Internally used in the `warn!` macro below. 39 | pub fn warn(s: String) { 40 | printerrln(format!("*** Warning: {}", s)[]); 41 | } 42 | 43 | /// Exits with a formatted error message. 44 | macro_rules! die( 45 | ($($e:expr),+) => (::ui::common::die(format!($($e),+))) 46 | ) 47 | 48 | /// Prints a formatted warning message. 49 | macro_rules! warn( 50 | ($($e:expr),+) => (::ui::common::warn(format!($($e),+))) 51 | ) 52 | 53 | /// Checks if the user pressed the escape key or the quit button. `atexit` is called before 54 | /// the program is terminated. 55 | pub fn check_exit(atexit: ||) { 56 | loop { 57 | match event::poll_event() { 58 | event::Event::Key(event::Key::Escape,_,_,_) | event::Event::Quit => { 59 | atexit(); 60 | exit(0); 61 | }, 62 | event::Event::None => { break; }, 63 | _ => {} 64 | } 65 | } 66 | } 67 | 68 | /// Writes a line to the console without advancing to the next line. `s` should be short enough 69 | /// to be replaced (currently up to 72 bytes). 70 | pub fn update_line(s: &str) { 71 | printerr(format!("\r{:72}\r{}", "", s)[]); 72 | } 73 | 74 | /// Reads a path string from the user in the platform-dependent way. Returns `None` if the user 75 | /// refused to do so or the platform is unsupported. 76 | #[cfg(target_os = "windows")] 77 | pub fn get_path_from_dialog() -> Option { 78 | use std::mem; 79 | use std::ptr::{null, null_mut}; 80 | use util::std::str::StrUtil; 81 | use ext::win32; 82 | 83 | let filter = 84 | "All Be-Music Source File (*.bms;*.bme;*.bml;*.pms)\x00*.bms;*.bme;*.bml;*.pms\x00\ 85 | Be-Music Source File (*.bms)\x00*.bms\x00\ 86 | Extended Be-Music Source File (*.bme)\x00*.bme\x00\ 87 | Longnote Be-Music Source File (*.bml)\x00*.bml\x00\ 88 | Po-Mu Source File (*.pms)\x00*.pms\x00\ 89 | All Files (*.*)\x00*.*\x00"; 90 | filter.as_utf16_c_str(|filter| { 91 | "Choose a file to play".as_utf16_c_str(|title| { 92 | let mut buf = [0u16, ..512]; 93 | let ofnsz = mem::size_of::(); 94 | let ofn = win32::ll::OPENFILENAMEW { 95 | lStructSize: ofnsz as libc::DWORD, 96 | lpstrFilter: filter, 97 | lpstrFile: buf.as_mut_ptr(), 98 | nMaxFile: buf.len() as libc::DWORD, 99 | lpstrTitle: title, 100 | Flags: win32::ll::OFN_HIDEREADONLY, 101 | 102 | // zero-initialized fields 103 | hwndOwner: null_mut(), hInstance: null_mut(), 104 | lpstrCustomFilter: null_mut(), nMaxCustFilter: 0, nFilterIndex: 0, 105 | lpstrFileTitle: null_mut(), nMaxFileTitle: 0, 106 | lpstrInitialDir: null(), nFileOffset: 0, nFileExtension: 0, 107 | lpstrDefExt: null(), lCustData: 0, lpfnHook: null_mut(), 108 | lpTemplateName: null(), pvReserved: null_mut(), 109 | dwReserved: 0, FlagsEx: 0, 110 | }; 111 | let ret = unsafe {win32::ll::GetOpenFileNameW(mem::transmute(&ofn))}; 112 | if ret != 0 { 113 | let path: &[u16] = match buf.position_elem(&0) { 114 | Some(idx) => buf[..idx], 115 | None => buf[] 116 | }; 117 | // path may result in an invaild UTF-16 path (but valid UCS-2 one) 118 | String::from_utf16(path).map(|s| Path::new(s[])) 119 | } else { 120 | None 121 | } 122 | }) 123 | }) 124 | } 125 | 126 | /// Reads a path string from the user in the platform-dependent way. Returns `None` if the user 127 | /// refused to do so or the platform is unsupported. 128 | #[cfg(not(target_os = "windows"))] 129 | pub fn get_path_from_dialog() -> Option { 130 | None 131 | } 132 | 133 | /// A periodic timer for thresholding the rate of information display. 134 | pub struct Ticker { 135 | /// Minimal required milliseconds after the last display. 136 | pub interval: uint, 137 | /// The timestamp at the last display. It is a return value from `sdl::get_ticks` and 138 | /// measured in milliseconds. May be a `None` if the ticker is at the initial state or 139 | /// has been reset by `reset` method. 140 | pub lastinfo: Option 141 | } 142 | 143 | impl Ticker { 144 | /// Returns a new ticker with a default display interval. 145 | pub fn new() -> Ticker { 146 | /// A reasonable interval for the console and graphic display. Currently set to about 21fps. 147 | const INFO_INTERVAL: uint = 47; 148 | Ticker::with_interval(INFO_INTERVAL) 149 | } 150 | 151 | /// Same as `Ticker::new` but uses a custom display interval. Interval of zero makes the ticker 152 | /// tick every time `on_tick` is called. 153 | pub fn with_interval(interval: uint) -> Ticker { 154 | Ticker { interval: interval, lastinfo: None } 155 | } 156 | 157 | /// Calls `f` only when required milliseconds have passed after the last display. 158 | /// `now` should be a return value from `sdl::get_ticks`. 159 | pub fn on_tick(&mut self, now: uint, f: ||) { 160 | if self.lastinfo.map_or(true, |t| now - t >= self.interval) { 161 | self.lastinfo = Some(now); 162 | f(); 163 | } 164 | } 165 | 166 | /// Lets the next call to `on_tick` always call the callback. 167 | pub fn reset(&mut self) { 168 | self.lastinfo = None; 169 | } 170 | } 171 | 172 | -------------------------------------------------------------------------------- /src/ui/hooks.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md and LICENSE.txt for details. 4 | 5 | //! Common skin hooks for various types. 6 | 7 | use format::{timeline, metadata, bms}; 8 | use engine::{keyspec, player}; 9 | use ui::options; 10 | 11 | use gfx::skin::scalar::{Scalar, IntoScalar}; 12 | use gfx::skin::hook::Hook; 13 | 14 | define_hooks! { 15 | for options::Options |opts, id, parent, body| { 16 | scalar "opts.playspeed" => opts.playspeed.into_scalar(); 17 | 18 | block "opts.autoplay" => opts.is_autoplay() && body(parent, ""); 19 | block "opts.modifier" => match opts.modf { 20 | Some(options::Modifier::Mirror) => { body(parent, "mirror"); } 21 | Some(options::Modifier::Shuffle) => { body(parent, "shuffle"); } 22 | Some(options::Modifier::ShuffleEx) => { body(parent, "shuffle-ex"); } 23 | Some(options::Modifier::Random) => { body(parent, "random"); } 24 | Some(options::Modifier::RandomEx) => { body(parent, "random-ex"); } 25 | None => {} 26 | }; 27 | block "opts.hasbga" => opts.has_bga() && body(parent, ""); 28 | block "opts.hasmovie" => opts.has_movie() && body(parent, ""); 29 | block "opts.showinfo" => opts.showinfo && body(parent, ""); 30 | block "opts.fullscreen" => opts.fullscreen && body(parent, ""); 31 | } 32 | } 33 | 34 | impl Hook for timeline::Timeline { 35 | fn scalar_hook<'a>(&'a self, id: &str) -> Option> { 36 | match id { 37 | "timeline.initbpm" => Some(self.initbpm.into_scalar()), 38 | _ => None, 39 | } 40 | } 41 | } 42 | 43 | define_hooks! { 44 | for timeline::TimelineInfo |infos, id, parent, body| { 45 | scalar "timeline.nnotes" => infos.nnotes.into_scalar(); 46 | scalar "timeline.maxscore" => infos.maxscore.into_scalar(); 47 | 48 | block "timeline.bpmchange" => infos.hasbpmchange && body(parent, ""); 49 | block "timeline.longnote" => infos.haslongnote && body(parent, ""); 50 | } 51 | } 52 | 53 | define_hooks! { 54 | for metadata::Meta |meta, id, parent, body| { 55 | scalar "meta.title" => return meta.title.as_ref().map(|s| s.as_scalar()); 56 | scalar "meta.genre" => return meta.genre.as_ref().map(|s| s.as_scalar()); 57 | scalar "meta.artist" => return meta.artist.as_ref().map(|s| s.as_scalar()); 58 | scalar "meta.level" => return meta.level.as_ref().map(|lv| lv.value.into_scalar()); 59 | scalar "meta.difficulty" => 60 | return meta.difficulty.map(|metadata::Difficulty(diff)| diff.into_scalar()); 61 | 62 | block "meta.title" => meta.title.is_some() && body(parent, ""); 63 | block "meta.subtitle" => 64 | meta.subtitles.iter().all(|s| 65 | body(&parent.add_text("meta.subtitle", s[]), "")); 66 | block "meta.genre" => meta.genre.is_some() && body(parent, ""); 67 | block "meta.artist" => meta.artist.is_some() && body(parent, ""); 68 | block "meta.subartist" => 69 | meta.subartists.iter().all(|s| 70 | body(&parent.add_text("meta.subartist", s[]), "")); 71 | block "meta.comment" => 72 | meta.comments.iter().all(|s| 73 | body(&parent.add_text("meta.comment", s[]), "")); 74 | block "meta.level" => meta.level.is_some() && body(parent, ""); 75 | block "meta.levelsystem" => match meta.level.as_ref().map(|lv| lv.system) { 76 | Some(metadata::LevelSystem::Bms) => { body(parent, "bms"); } 77 | None => {} 78 | }; 79 | block "meta.difficulty" => match meta.difficulty { 80 | Some(metadata::Difficulty(1)) => { body(parent, "beginner"); } 81 | Some(metadata::Difficulty(2)) => { body(parent, "normal"); } 82 | Some(metadata::Difficulty(3)) => { body(parent, "hard"); } 83 | Some(metadata::Difficulty(4)) => { body(parent, "extra"); } 84 | Some(metadata::Difficulty(5)) => { body(parent, "insane"); } 85 | Some(metadata::Difficulty(_)) => { body(parent, "???"); } 86 | None => {} 87 | }; 88 | } 89 | 90 | for bms::BmsMeta |meta, id, parent, body| { 91 | delegate meta.common; 92 | 93 | scalar "meta.encoding" => meta.encoding.val0().into_scalar(); 94 | scalar "meta.encoding.confidence" => { 95 | let mut confidence = meta.encoding.val1(); 96 | if confidence > 1.0 { confidence = 1.0; } 97 | confidence.into_scalar() 98 | }; 99 | 100 | block "meta.playmode" => match meta.mode { 101 | bms::PlayMode::Single => { body(parent, "single"); } 102 | bms::PlayMode::Couple => { body(parent, "couple"); } 103 | bms::PlayMode::Double => { body(parent, "double"); } 104 | bms::PlayMode::Battle => { body(parent, "battle"); } 105 | }; 106 | } 107 | 108 | for bms::Bms |bms, id, parent, body| { 109 | delegate bms.meta; 110 | delegate bms.timeline; 111 | } 112 | 113 | for keyspec::KeySpec |keyspec, id, parent, body| { 114 | scalar "meta.nkeys" => keyspec.nkeys().into_scalar(); 115 | } 116 | } 117 | 118 | struct GradeInfo { 119 | name: &'static str, 120 | count: uint, 121 | } 122 | 123 | define_hooks! { 124 | for GradeInfo |grade, id, parent, body| { 125 | scalar "grade.name" => grade.name.into_scalar(); 126 | scalar "grade.count" => grade.count.into_scalar(); 127 | } 128 | 129 | for player::Player |player, id, parent, body| { 130 | delegate player.opts; 131 | delegate player.meta; 132 | delegate player.timeline; 133 | delegate player.infos; 134 | delegate player.keyspec; 135 | 136 | scalar "meta.duration" => player.duration.into_scalar(); 137 | scalar "player.playspeed" => player.nominal_playspeed().into_scalar(); 138 | scalar "player.bpm" => player.bpm.into_scalar(); 139 | scalar "player.now.time" => player.now.into_scalar(); 140 | scalar "player.now.vpos" => player.cur.loc.vpos.into_scalar(); 141 | scalar "player.ratio" => { 142 | let starttime = (player.now - player.origintime) as f64; 143 | let duration = player.duration * 1000.0; 144 | (starttime / duration).into_scalar() 145 | }; 146 | scalar "player.score" => player.score.into_scalar(); 147 | scalar "player.lastcombo" => player.lastcombo.into_scalar(); 148 | scalar "player.bestcombo" => player.bestcombo.into_scalar(); 149 | scalar "player.gauge" => 150 | (player.gauge as f64 / player::MAXGAUGE as f64).into_scalar(); 151 | scalar "player.survival" => 152 | (player.survival as f64 / player::MAXGAUGE as f64).into_scalar(); 153 | block "player.survival" => player.gauge >= player.survival && body(parent, ""); 154 | block "player.grades" => { 155 | static GRADENAMES: [&'static str, ..5] = ["cool", "great", "good", "bad", "miss"]; 156 | GRADENAMES.iter().zip(player.gradecounts.iter().rev()).all(|(&name, &count)| 157 | body(&parent.delegate(&GradeInfo { name: name, count: count }), name)); 158 | }; 159 | } 160 | } 161 | 162 | -------------------------------------------------------------------------------- /src/ext/smpeg.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md for details. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or 6 | // the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! The minimal but functional binding for SMPEG. 10 | 11 | use libc::{c_int, c_float}; 12 | use std::ptr::null_mut; 13 | use std::mem::transmute; 14 | use sdl::video::Surface; 15 | pub use self::ll::SMPEGstatus; 16 | pub use self::ll::SMPEGstatus::{SMPEG_ERROR, SMPEG_STOPPED, SMPEG_PLAYING}; 17 | 18 | pub mod ll { 19 | #![allow(non_camel_case_types)] 20 | 21 | use libc::{c_void, c_int, c_char, c_float, c_double}; 22 | use sdl::video::ll::{SDL_RWops, SDL_Surface}; 23 | use sdl::audio::ll::SDL_AudioSpec; 24 | #[repr(C)] 25 | pub struct SMPEG { _opaque: () } 26 | #[repr(C)] 27 | pub struct SMPEG_Info { 28 | pub has_audio: c_int, 29 | pub has_video: c_int, 30 | pub width: c_int, 31 | pub height: c_int, 32 | pub current_frame: c_int, 33 | pub current_fps: c_double, 34 | pub audio_string: [c_char, ..80], 35 | pub audio_current_frame: c_int, 36 | pub current_offset: u32, 37 | pub total_size: u32, 38 | pub current_time: c_double, 39 | pub total_time: c_double 40 | } 41 | #[deriving(PartialEq, Eq, Clone)] 42 | #[repr(C)] 43 | pub enum SMPEGstatus { 44 | SMPEG_ERROR = -1, 45 | SMPEG_STOPPED = 0, 46 | SMPEG_PLAYING =1 47 | } 48 | #[link(name = "smpeg")] 49 | extern { 50 | pub fn SMPEG_new(file: *const c_char, info: *mut SMPEG_Info, 51 | sdl_audio: c_int) -> *mut SMPEG; 52 | pub fn SMPEG_new_descr(file: c_int, info: *mut SMPEG_Info, 53 | sdl_audio: c_int) -> *mut SMPEG; 54 | pub fn SMPEG_new_data(data: *mut c_void, size: c_int, info: *mut SMPEG_Info, 55 | sdl_audio: c_int) -> *mut SMPEG; 56 | pub fn SMPEG_new_rwops(src: *mut SDL_RWops, info: *mut SMPEG_Info, 57 | sdl_audio: c_int) -> *mut SMPEG; 58 | pub fn SMPEG_getinfo(mpeg: *mut SMPEG, info: *mut SMPEG_Info); 59 | pub fn SMPEG_enableaudio(mpeg: *mut SMPEG, enable: c_int); 60 | pub fn SMPEG_enablevideo(mpeg: *mut SMPEG, enable: c_int); 61 | pub fn SMPEG_delete(mpeg: *mut SMPEG); 62 | pub fn SMPEG_status(mpeg: *mut SMPEG) -> SMPEGstatus; 63 | pub fn SMPEG_setvolume(mpeg: *mut SMPEG, volume: c_int); 64 | // XXX SDL_Mutex and SMPEG_DisplayCallback unimplemented 65 | pub fn SMPEG_setdisplay(mpeg: *mut SMPEG, dst: *mut SDL_Surface, 66 | surfLock: *mut c_void, callback: *mut c_void); 67 | pub fn SMPEG_loop(mpeg: *mut SMPEG, repeat: c_int); 68 | pub fn SMPEG_scaleXY(mpeg: *mut SMPEG, width: c_int, height: c_int); 69 | pub fn SMPEG_scale(mpeg: *mut SMPEG, scale: c_int); 70 | pub fn SMPEG_move(mpeg: *mut SMPEG, x: c_int, y: c_int); 71 | pub fn SMPEG_setdisplayregion(mpeg: *mut SMPEG, x: c_int, y: c_int, w: c_int, h: c_int); 72 | pub fn SMPEG_play(mpeg: *mut SMPEG); 73 | pub fn SMPEG_pause(mpeg: *mut SMPEG); 74 | pub fn SMPEG_stop(mpeg: *mut SMPEG); 75 | pub fn SMPEG_rewind(mpeg: *mut SMPEG); 76 | pub fn SMPEG_seek(mpeg: *mut SMPEG, bytes: c_int); 77 | pub fn SMPEG_skip(mpeg: *mut SMPEG, seconds: c_float); 78 | pub fn SMPEG_renderFrame(mpeg: *mut SMPEG, framenum: c_int); 79 | pub fn SMPEG_renderFinal(mpeg: *mut SMPEG, dst: *mut SDL_Surface, x: c_int, y: c_int); 80 | // XXX SMPEG_Filter unimplemented 81 | pub fn SMPEG_filter(mpeg: *mut SMPEG, filter: *mut c_void) -> *mut c_void; 82 | pub fn SMPEG_error(mpeg: *mut SMPEG) -> *mut c_char; 83 | pub fn SMPEG_playAudio(mpeg: *mut SMPEG, stream: *mut u8, len: c_int) -> c_int; 84 | pub fn SMPEG_playAudioSDL(mpeg: *mut c_void, stream: *mut u8, len: c_int) -> c_int; 85 | pub fn SMPEG_wantedSpec(mpeg: *mut SMPEG, wanted: *mut SDL_AudioSpec) -> c_int; 86 | pub fn SMPEG_actualSpec(mpeg: *mut SMPEG, spec: *mut SDL_AudioSpec); 87 | } 88 | } 89 | 90 | pub struct MPEG { 91 | pub raw: *mut ll::SMPEG 92 | } 93 | 94 | fn wrap_mpeg(raw: *mut ll::SMPEG) -> MPEG { 95 | MPEG { raw: raw } 96 | } 97 | 98 | impl Drop for MPEG { 99 | fn drop(&mut self) { 100 | unsafe { ll::SMPEG_delete(self.raw); } 101 | } 102 | } 103 | 104 | impl MPEG { 105 | pub fn from_path(path: &Path) -> Result { 106 | let raw = unsafe { 107 | let path = path.to_c_str(); 108 | ll::SMPEG_new(path.as_ptr(), null_mut(), 0) 109 | }; 110 | 111 | if raw.is_null() { Err(::sdl::get_error()) } 112 | else { Ok(wrap_mpeg(raw)) } 113 | } 114 | 115 | pub fn status(&self) -> SMPEGstatus { 116 | unsafe { ll::SMPEG_status(self.raw) } 117 | } 118 | 119 | pub fn set_volume(&self, volume: int) { 120 | unsafe { ll::SMPEG_setvolume(self.raw, volume as c_int); } 121 | } 122 | 123 | pub fn set_display(&self, surface: &Surface) { 124 | unsafe { 125 | ll::SMPEG_setdisplay(self.raw, surface.raw, null_mut(), null_mut()); 126 | } 127 | } 128 | 129 | pub fn enable_video(&self, enable: bool) { 130 | unsafe { ll::SMPEG_enablevideo(self.raw, enable as c_int); } 131 | } 132 | 133 | pub fn enable_audio(&self, enable: bool) { 134 | unsafe { ll::SMPEG_enableaudio(self.raw, enable as c_int); } 135 | } 136 | 137 | pub fn set_loop(&self, repeat: bool) { 138 | unsafe { ll::SMPEG_loop(self.raw, repeat as c_int); } 139 | } 140 | 141 | pub fn resize(&self, width: int, height: int) { 142 | unsafe { ll::SMPEG_scaleXY(self.raw, width as c_int, height as c_int); } 143 | } 144 | 145 | pub fn scale_by(&self, scale: int) { 146 | unsafe { ll::SMPEG_scale(self.raw, scale as c_int); } 147 | } 148 | 149 | pub fn move_by(&self, x: int, y: int) { 150 | unsafe { ll::SMPEG_move(self.raw, x as c_int, y as c_int); } 151 | } 152 | 153 | pub fn set_display_region(&self, x: int, y: int, w: int, h: int) { 154 | unsafe { 155 | ll::SMPEG_setdisplayregion(self.raw, x as c_int, y as c_int, 156 | w as c_int, h as c_int); 157 | } 158 | } 159 | 160 | pub fn play(&self) { 161 | unsafe { ll::SMPEG_play(self.raw); } 162 | } 163 | 164 | pub fn pause(&self) { 165 | unsafe { ll::SMPEG_pause(self.raw); } 166 | } 167 | 168 | pub fn stop(&self) { 169 | unsafe { ll::SMPEG_stop(self.raw); } 170 | } 171 | 172 | pub fn rewind(&self) { 173 | unsafe { ll::SMPEG_rewind(self.raw); } 174 | } 175 | 176 | pub fn seek(&self, bytes: int) { 177 | unsafe { ll::SMPEG_seek(self.raw, bytes as c_int); } 178 | } 179 | 180 | pub fn skip(&self, seconds: f64) { 181 | unsafe { ll::SMPEG_skip(self.raw, seconds as c_float); } 182 | } 183 | 184 | pub fn get_error(&self) -> String { 185 | unsafe { 186 | let cstr = ll::SMPEG_error(self.raw); 187 | String::from_raw_buf(transmute(&cstr)) 188 | } 189 | } 190 | } 191 | 192 | -------------------------------------------------------------------------------- /src/format/bms/mod.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md and LICENSE.txt for details. 4 | 5 | /*! 6 | * BMS format implementation. 7 | * 8 | * # Structure 9 | * 10 | * The BMS format is a plain text format with most directives start with optional whitespace 11 | * followed by `#`. Besides the metadata (title, artist etc.), a BMS file is a map from the time 12 | * position to various game play elements (henceforth "objects") and other object-like effects 13 | * including BGM and BGA changes. It also contains preprocessor directives used to randomize some or 14 | * all parts of the BMS file, which would only make sense in the loading time. 15 | * 16 | * The time position is a virtual time divided by an unit of (musical) measure. It is related to 17 | * the actual time by the current Beats Per Minute (BPM) value which can, well, also change during 18 | * the game play. Consequently it is convenient to refer the position in terms of measures, which 19 | * the BMS format does: the lines `#xxxyy:AABBCC...` indicates that the measure number `xxx` 20 | * contains objects or object-like effects (of the type specified by `yy`, henceforth "channels"), 21 | * evenly spaced throughout the measure and which data values are `AA`, `BB`, `CC` respectively. 22 | * 23 | * An alphanumeric identifier (henceforth "alphanumeric key") like `AA` or `BB` may mean that 24 | * the actual numeric value interpreted as base 16 or 36 (depending on the channel), or a reference 25 | * to other assets (e.g. `#BMPAA foo.png`) or complex values specified by other commands (e.g. 26 | * `#BPMBB 192.0`). In most cases, an identifier `00` indicates an absence of objects or object-like 27 | * effects at that position. 28 | * 29 | * More detailed information about BMS format, including surveys about how different implementations 30 | * (so called BMS players) react to underspecified features or edge cases, can be found at [BMS 31 | * command memo](http://hitkey.nekokan.dyndns.info/cmds.htm). 32 | */ 33 | 34 | use std::fmt; 35 | 36 | use format::obj::BPM; 37 | use format::metadata::Meta; 38 | use format::timeline::Timeline; 39 | use format::pointer::Pointer; 40 | 41 | pub use format::bms::types::{Key, MAXKEY}; 42 | 43 | pub mod types; 44 | pub mod diag; 45 | pub mod encoding; 46 | pub mod preproc; 47 | pub mod parse; 48 | pub mod load; 49 | 50 | /// Sound reference. 51 | #[deriving(PartialEq,Eq,Clone)] 52 | pub struct SoundRef(pub Key); 53 | 54 | /// Image reference. 55 | #[deriving(PartialEq,Eq,Clone)] 56 | pub struct ImageRef(pub Key); 57 | 58 | impl Deref for SoundRef { 59 | fn deref<'a>(&'a self) -> &'a Key { 60 | let SoundRef(ref key) = *self; 61 | key 62 | } 63 | } 64 | 65 | impl Deref for ImageRef { 66 | fn deref<'a>(&'a self) -> &'a Key { 67 | let ImageRef(ref key) = *self; 68 | key 69 | } 70 | } 71 | 72 | impl fmt::Show for SoundRef { 73 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.deref().fmt(f) } 74 | } 75 | 76 | impl fmt::Show for ImageRef { 77 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.deref().fmt(f) } 78 | } 79 | 80 | /// Default BPM. This value comes from the original BMS specification. 81 | pub const DEFAULT_BPM: BPM = BPM(130.0); 82 | 83 | /// Play mode specified in the BMS file. This maps to BMS #PLAYER command. Over the course of 84 | /// the evolution of the BMS format, this value became highly ambiguous and the client is advised 85 | /// not to solely rely on this value. 86 | #[deriving(PartialEq,Eq,Clone)] 87 | pub enum PlayMode { 88 | /// Single Play (SP), where only channels #1x are used for the game play. 89 | Single = 1, 90 | /// Couple Play, where channels #1x and #2x renders to the different panels. They are originally 91 | /// meant to be played by different players with separate gauges and scores, but this mode of 92 | /// game play is increasingly unsupported by modern implementations. Sonorous has only a limited 93 | /// support for Couple Play. 94 | Couple = 2, 95 | /// Double Play (DP), where both channels #1x and #2x renders to a single wide panel. The chart 96 | /// is still meant to be played by one person. 97 | Double = 3, 98 | /// Battle Play, where channels #1x are copied to channels #2x and both renders to 99 | /// the different panels. This was a temporary measure for the two-player game mode and 100 | /// has been completely replaced by automatic support for two-player mode (or a lack thereof) 101 | /// in modern implementations. Sonorous does not support Battle Play, but it does parse and 102 | /// treats Battle Play as Single Play. 103 | Battle = 4, 104 | } 105 | 106 | /// Loaded BMS metadata and resources. 107 | pub struct BmsMeta { 108 | /// Common metadata. 109 | pub common: Meta, 110 | 111 | /// The name of character encoding used by the BMS file, and its confidence between 0 and 1. 112 | /// Confidence is set to infinity when it is forced by the loader. 113 | pub encoding: (&'static str, f64), 114 | 115 | /// Path to an image for loading screen. Maps to BMS #STAGEFILE command. 116 | pub stagefile: Option, 117 | /// Path to an image for banner image for the selection screen. Maps to BMS #BANNER command. 118 | pub banner: Option, 119 | /// A base path used for loading all other resources. Maps to BMS #PATH_WAV command. 120 | pub basepath: Option, 121 | 122 | /// Game mode. Maps to BMS #PLAYER command. 123 | pub mode: PlayMode, 124 | /// Gauge difficulty. Higher is easier. Maps to BMS #RANK command. 125 | pub rank: int, 126 | 127 | /// Paths to sound file relative to `basepath` or BMS file. 128 | pub sndpath: Vec>, 129 | /// Paths to image/movie file relative to `basepath` or BMS file. 130 | pub imgpath: Vec>, 131 | } 132 | 133 | /// Timeline for the BMS file. 134 | pub type BmsTimeline = Timeline; 135 | 136 | /// Pointer for the BMS file. Provided for the convenience. 137 | pub type BmsPointer = Pointer; 138 | 139 | /// Loaded BMS data. This is an intermediate structure for metadata, resources and actual chart 140 | /// data, and cannot be used for actual game play (since `Pointer` requires `Timeline` to be 141 | /// a managed pointer). 142 | pub struct Bms { 143 | /// A path to the BMS file if any. Also used for finding the resource when `meta.basepath` 144 | /// is not set. 145 | pub bmspath: Option, 146 | /// Metadata and resources. 147 | pub meta: BmsMeta, 148 | /// Timeline. 149 | pub timeline: BmsTimeline, 150 | } 151 | 152 | impl Bms { 153 | /// Sets a path to the BMS file, which affects some metadata. 154 | pub fn with_bmspath(self, bmspath: &Path) -> Bms { 155 | let Bms { bmspath: bmspath0, meta, timeline } = self; 156 | if bmspath0.is_some() { 157 | Bms { bmspath: bmspath0, meta: meta, timeline: timeline } 158 | } else { 159 | let bmspath = bmspath.clone(); 160 | let mut meta = meta; 161 | if meta.basepath.is_none() { 162 | let basepath = bmspath.dir_path(); 163 | // Rust: `""` is not same as `"."` but `Path::dir_path` may produce it! 164 | let basepath = if basepath == Path::new("") {Path::new(".")} else {basepath}; 165 | meta.basepath = Some(basepath); 166 | } 167 | Bms { bmspath: Some(bmspath), meta: meta, timeline: timeline } 168 | } 169 | } 170 | } 171 | 172 | -------------------------------------------------------------------------------- /src/format/bms/types.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md and LICENSE.txt for details. 4 | 5 | //! Common types for BMS format. 6 | 7 | use std::{str, fmt}; 8 | use util::lex::FromStrPrefix; 9 | use format::obj::Lane; 10 | 11 | /// Two-letter alphanumeric identifier used for virtually everything, including resource 12 | /// management, variable BPM and chart specification. 13 | #[deriving(PartialEq,PartialOrd,Eq,Ord,Clone)] 14 | pub struct Key(pub int); 15 | 16 | /// The number of all possible alphanumeric keys. 17 | pub const MAXKEY: int = 36*36; 18 | 19 | impl Deref for Key { 20 | fn deref<'a>(&'a self) -> &'a int { 21 | let Key(ref v) = *self; 22 | v 23 | } 24 | } 25 | 26 | /// All base-36 digits. 27 | static BASE36_MAP: &'static [u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 28 | 29 | /// Converts a single alphanumeric (base-36) letter to an integer. 30 | fn getdigit(n: char) -> Option { 31 | match n { 32 | '0'...'9' => Some((n as int) - ('0' as int)), 33 | 'a'...'z' => Some((n as int) - ('a' as int) + 10), 34 | 'A'...'Z' => Some((n as int) - ('A' as int) + 10), 35 | _ => None 36 | } 37 | } 38 | 39 | impl Key { 40 | /// Returns a definitely invalid alphanumeric key. 41 | pub fn dummy() -> Key { 42 | Key(-1) 43 | } 44 | 45 | /// Converts the first two letters of `s` to a `Key`. 46 | pub fn from_chars(s: &[char]) -> Option { 47 | if s.len() < 2 { return None; } 48 | getdigit(s[0]).and_then(|a| { 49 | getdigit(s[1]).map(|b| Key(a * 36 + b)) 50 | }) 51 | } 52 | 53 | /// Converts the first two letters of `s` to a `Key`. 54 | pub fn from_str(s: &str) -> Option { 55 | if s.len() < 2 { return None; } 56 | let str::CharRange {ch:c1, next:p1} = s.char_range_at(0); 57 | getdigit(c1).and_then(|a| { 58 | let str::CharRange {ch:c2, next:p2} = s.char_range_at(p1); 59 | getdigit(c2).map(|b| { 60 | assert!(p2 == 2); // both characters should be in ASCII 61 | Key(a * 36 + b) 62 | }) 63 | }) 64 | } 65 | 66 | /// Returns if the alphanumeric key is in the proper range. Sonorous supports the full 67 | /// range of 00-ZZ (0-1295) for every case. 68 | pub fn is_valid(&self) -> bool { 69 | 0 <= **self && **self < MAXKEY 70 | } 71 | 72 | /// Re-reads the alphanumeric key as a hexadecimal number if possible. This is required 73 | /// due to handling of channel #03 (BPM is expected to be in hexadecimal). 74 | pub fn to_hex(&self) -> Option { 75 | let sixteens = **self / 36; 76 | let ones = **self % 36; 77 | if sixteens < 16 && ones < 16 {Some(sixteens * 16 + ones)} else {None} 78 | } 79 | 80 | /// Converts the channel number to the lane number. 81 | pub fn to_lane(&self) -> Lane { 82 | let player = match **self / 36 { 83 | 1 | 3 | 5 | 0xD => 0, 84 | 2 | 4 | 6 | 0xE => 1, 85 | _ => panic!("non-object channel") 86 | }; 87 | Lane(player * 36 + **self as uint % 36) 88 | } 89 | } 90 | 91 | impl FromStrPrefix for Key { 92 | fn from_str_prefix<'a>(s: &'a str) -> Option<(Key, &'a str)> { 93 | Key::from_str(s).map(|key| (key, s[2..])) 94 | } 95 | } 96 | 97 | impl fmt::Show for Key { 98 | /// Returns a two-letter representation of alphanumeric key. 99 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 100 | assert!(self.is_valid()); 101 | let sixteens = **self / 36; 102 | let ones = **self % 36; 103 | write!(f, "{}{}", BASE36_MAP[sixteens as uint] as char, 104 | BASE36_MAP[ones as uint] as char) 105 | } 106 | } 107 | 108 | /// Same as `Key` but accepts one-letter alphanumeric keys, encoded as a negative number 109 | /// from -1 (`0`) to -36 (`Z`) which is invalid in a plain `Key`. 110 | #[deriving(PartialEq,PartialOrd,Clone)] 111 | pub struct PartialKey(pub int); 112 | 113 | impl Deref for PartialKey { 114 | fn deref<'a>(&'a self) -> &'a int { 115 | let PartialKey(ref v) = *self; 116 | v 117 | } 118 | } 119 | 120 | impl PartialKey { 121 | /// Returns a definitely invalid partial alphanumeric key. 122 | pub fn dummy() -> PartialKey { 123 | PartialKey(-37) 124 | } 125 | 126 | /// Converts the first one or two letters of `s` to a `PartialKey`. 127 | /// Also returns a remaining portion of `s`. 128 | pub fn from_chars<'r>(s: &'r [char]) -> Option<(PartialKey, &'r [char])> { 129 | if s.len() < 1 { return None; } 130 | getdigit(s[0]).map(|a| { 131 | if s.len() < 2 { 132 | (PartialKey(-a - 1), s[1..]) 133 | } else { 134 | match getdigit(s[1]) { 135 | Some(b) => (PartialKey(a * 36 + b), s[2..]), 136 | None => (PartialKey(-a - 1), s[1..]) 137 | } 138 | } 139 | }) 140 | } 141 | 142 | /// Converts the first one or two letters of `s` to a `PartialKey`. 143 | /// Also returns a remaining portion of `s`. 144 | pub fn from_str<'r>(s: &'r str) -> Option<(PartialKey, &'r str)> { 145 | if s.len() < 1 { return None; } 146 | let str::CharRange {ch:c1, next:p1} = s.char_range_at(0); 147 | getdigit(c1).map(|a| { 148 | assert!(p1 == 1); // c1 should be in ASCII 149 | if s.len() < 2 { // do not advance 150 | (PartialKey(-a - 1), s[p1..]) 151 | } else { 152 | let str::CharRange {ch:c2, next:p2} = s.char_range_at(p1); 153 | match getdigit(c2) { 154 | Some(b) => { 155 | assert!(p2 == 2); // both characters should be in ASCII 156 | (PartialKey(a * 36 + b), s[p2..]) 157 | }, 158 | None => (PartialKey(-a - 1), s[p1..]) 159 | } 160 | } 161 | }) 162 | } 163 | 164 | /// Returns if the alphanumeric key is in the proper range. 165 | /// In addition to `Key`s 00-ZZ (0 to 1295), `PartialKey` supports 0-Z (-1 to -36). 166 | pub fn is_valid(&self) -> bool { 167 | -36 <= **self && **self < MAXKEY 168 | } 169 | 170 | /// Returns if the alphanumeric key is one-digit long. 171 | pub fn is_partial(&self) -> bool { 172 | **self < 0 173 | } 174 | 175 | /// Re-reads the alphanumeric key as a hexadecimal number if possible. 176 | pub fn to_hex(&self) -> Option { 177 | let (sixteens, ones) = if **self < 0 {(0, -**self - 1)} else {(**self / 36, **self % 36)}; 178 | if sixteens < 16 && ones < 16 {Some(sixteens * 16 + ones)} else {None} 179 | } 180 | 181 | /// Converts a partial alphanumeric key into a `Key`, assuming it's missing a leading `0`. 182 | pub fn into_key(self) -> Key { 183 | assert!(self.is_valid()); 184 | if *self < 0 {Key(-*self - 1)} else {Key(*self)} 185 | } 186 | } 187 | 188 | impl FromStrPrefix for PartialKey { 189 | fn from_str_prefix<'a>(s: &'a str) -> Option<(PartialKey, &'a str)> { 190 | PartialKey::from_str(s) 191 | } 192 | } 193 | 194 | impl fmt::Show for PartialKey { 195 | /// Returns an one- or two-letter representation of alphanumeric key. 196 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 197 | assert!(self.is_valid()); 198 | if **self < 0 { 199 | write!(f, "{}", BASE36_MAP[(-**self - 1) as uint] as char) 200 | } else { 201 | let sixteens = **self / 36; 202 | let ones = **self % 36; 203 | write!(f, "{}{}", BASE36_MAP[sixteens as uint] as char, 204 | BASE36_MAP[ones as uint] as char) 205 | } 206 | } 207 | } 208 | 209 | -------------------------------------------------------------------------------- /res/skin/selecting.cson: -------------------------------------------------------------------------------- 1 | # This is a part of Sonorous. 2 | # Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | # See README.md and LICENSE.txt for details. 4 | 5 | scalars: { 6 | logo = {$image: "../img/logo-prealpha.png"} 7 | } 8 | 9 | nodes: [ 10 | # entries 11 | [ 12 | {$clip: [[10,0], ["100%",300]]} 13 | {$rect: null 14 | at = [["100%-558","100%-125"], ["100%","100%"]] 15 | opacity = 0.2 16 | color = "white"} 17 | {$rect: "logo" 18 | at = [["100%-558","100%-125"], ["100%","100%"]] 19 | opacity = 0.4} 20 | {$$: "entries", $then: [ 21 | {$$: "entry.inverted", $then: [ 22 | {$rect: null 23 | at = [[0,0], ["100%",20]] 24 | color = "white"} 25 | ]} 26 | {$$: "entry.meta", $then: [ 27 | {$text: {$$: "meta.level", $then: {$: "meta.level", format = "00"}, $else: "--"} 28 | at = [2,2] 29 | size = 16 30 | color = "gray"} 31 | {$text: {$: "meta.title"} 32 | at = [26,2] 33 | size = 16 34 | color = {$$: "entry.inverted", $then: "black", $else: "white"}} 35 | ], $else: [ 36 | {$text: {$: "entry.path"} 37 | at = [2,2] 38 | size = 16 39 | color = {$$: "entry.inverted", $then: "#444", $else: "#ccc"}} 40 | ]} 41 | {$$: "entry.hash", $then: [ 42 | {$text: {$: "entry.hash"} 43 | at = ["100%-4",2] 44 | size = 16 45 | color = "gray" 46 | anchor = "right"} 47 | ]} 48 | {$cliptop: 20} 49 | ], $else: [ 50 | {$$: "scanning", $then: [], $else: [ 51 | {$text: "No BMS file found." 52 | at = [2,2] 53 | size = 16 54 | color = "gray"} 55 | ]} 56 | ]} 57 | ] 58 | {$rect: null 59 | at = [[2,"2+296*entries.scrollstart"], [7,"2+296*entries.scrollend"]] 60 | color = "#c0c0c0"} 61 | {$rect: null 62 | at = [[0,301], ["100%",302]] 63 | color = "white"} 64 | 65 | # preloaded data if any 66 | [ 67 | {$clip: [[0,300], ["100%","100%-20"]]} 68 | {$$: "preload", "loading": [ 69 | {$text: "loading..." 70 | at = [4,4] 71 | size = 16 72 | color = "silver"} 73 | ], "loaded": [[ 74 | # prints the banner first, so that the overflowing title etc. can overlap (for now) 75 | [ 76 | {$clip: [["100%-302",24], ["100%-2",104]]} 77 | {$$: "meta.banner", $then: [ 78 | {$rect: "meta.banner", at = [[0,0], ["100%","100%"]]} 79 | ], $else: [ 80 | {$line: null, from = [0,0], to = [0,"100%"], color = "white"} 81 | {$line: null, from = [0,0], to = ["100%",0], color = "white"} 82 | {$line: null, from = [0,0], to = ["100%","100%"], color = "white"} 83 | {$line: null, from = [0,"100%"], to = ["100%",0], color = "white"} 84 | {$line: null, from = [0,"100%"], to = ["100%","100%"], color = "white"} 85 | {$line: null, from = ["100%",0], to = ["100%","100%"], color = "white"} 86 | ]} 87 | ] 88 | 89 | {$text: [{$: "meta.duration", format = "..00:00.0"}, 90 | " | Level ", {$: "meta.level"}, 91 | " | BPM ", {$: "timeline.initbpm", format = "..0.00"}, 92 | {$$: "timeline.bpmchange", $then: "?"}, 93 | " | ", {$: "timeline.nnotes"}, 94 | {$$text: "timeline.nnotes", "1": " note", $default: " notes"}, 95 | " [", {$: "meta.nkeys"}, "KEY", 96 | {$$: "timeline.longnote", $then: "-LN"}, 97 | {$$: "meta.difficulty", 98 | "beginner": " BEGINNER", "normal": " NORMAL", "hard": " HARD", 99 | "extra": " EXTRA", "insane": " INSANE"}, "]"] 100 | at = ["100%-2",4] 101 | size = 16 102 | color = "gray" 103 | anchor = "right"} 104 | 105 | {$$: "meta.genre", $then: [ 106 | {$text: {$: "meta.genre"}, at = [4,4], size = 16, color = "silver"} 107 | ]} 108 | {$cliptop: 18} 109 | 110 | {$$: "meta.title", $then: [ 111 | {$text: {$: "meta.title"}, at = [6,6], size = 32, color = "gray"} 112 | {$text: {$: "meta.title"}, at = [4,4], size = 32, color = "white"} 113 | ], $else: [ 114 | {$text: "(no title)", at = [4,4], size = 32, color = "gray"} 115 | ]} 116 | {$cliptop: 36} 117 | 118 | {$$: "meta.subtitle", $then: [ 119 | {$$len: "meta.subtitle", $then: [ 120 | {$text: {$: "meta.subtitle"}, at = [21,5], size = 16, color = "gray"} 121 | {$text: {$: "meta.subtitle"}, at = [20,4], size = 16, color = "white"} 122 | {$cliptop: 18} 123 | ]} 124 | ]} 125 | 126 | {$$: "meta.artist", $then: [ 127 | {$text: {$: "meta.artist"}, at = [4,4], size = 16, color = "white"} 128 | {$cliptop: 18} 129 | ]} 130 | 131 | {$$: "meta.subartist", $then: [ 132 | {$$len: "meta.subartist", $then: [ 133 | {$text: {$: "meta.subartist"}, at = [20,4], size = 16, color = "white"} 134 | {$cliptop: 18} 135 | ]} 136 | ]} 137 | 138 | {$$: "meta.comment", $then: [ 139 | {$$len: "meta.comment", $then: [ 140 | {$text: ["> ", {$: "meta.comment"}], at = [4,4], size = 16, color = "#80ff80"} 141 | {$cliptop: 18} 142 | ]} 143 | ]} 144 | 145 | {$$: "messages", $then: [ 146 | {$text: [{$$: "msg.severity", "fatal": "* Fatal: ", 147 | "warning": "* Warning: ", 148 | "note": "* Note: "}, {$: "msg.text"}, 149 | {$$: "msg.line", $then: [" (line ", {$: "msg.line"}, ")"]}] 150 | at = [4,4] 151 | size = 16 152 | color = {$$: "msg.severity", "fatal": "#ff4040", 153 | "warning": "#ffff40", 154 | "note": "#40ffff"}} 155 | {$cliptop: 18} 156 | {$$: "msg", 157 | "legacy-encoding": [ 158 | {$text: [" (Detected \"", {$: "meta.encoding"}, 159 | "\" encoding with confidence ", 160 | {$: "meta.encoding.confidence", format = "##0.00 * 100"}, "%)"] 161 | at = [4,4] 162 | size = 16 163 | color = "#208080"} 164 | {$cliptop: 18} 165 | ], 166 | $default: []} 167 | ]} 168 | ]], "failed": [ 169 | {$text: ["error: ", {$: "preload.error"}] 170 | at = [4,4] 171 | size = 16 172 | color = "silver"} 173 | ]} 174 | ] 175 | 176 | # status bar, will overwrite any overflowing messages 177 | {$rect: null 178 | at = [[0,"100%-20"], ["100%","100%"]] 179 | color = "white"} 180 | {$text: ["Up/Down/PgUp/PgDn/Home/End: Select Enter: ", 181 | {$$: "opts.autoplay", $then: "Autoplay", $else: "Play"}, 182 | " F5: Refresh Esc: Quit"] 183 | at = [2,"100%-2"] 184 | size = 16 185 | color = "black" 186 | anchor = [0,1]} 187 | ] 188 | 189 | # vim: syn=javascript ts=4 sts=4 sw=4 et ai 190 | -------------------------------------------------------------------------------- /src/gfx/skin/hook.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md for details. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or 6 | // the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | /*! 10 | * Skin hooks. 11 | * 12 | * There are currently three kinds of hooks available: 13 | * 14 | * - **Scalar hooks** return a text, a reference to the texture, 15 | * or a scalar value that can be converted to the text, 16 | * - **Block hooks** calls the block (represented as a closure) zero or more times. 17 | * It can optionally supply the alternative name so that the matching alternative (if any) 18 | * gets called. The `parent` parameter is used for the hook delegation (see below). 19 | * The block may return `false`, which requests the hook to stop the iteration. 20 | * 21 | * Normally objects implement the hooks via overriding corresponding methods 22 | * or delegating hooks to other objects. 23 | * It is normal that the same name is shared for different kinds of hooks, 24 | * and such technique is often used for optionally available scalars. 25 | * 26 | * Block hooks deserve some additional restrictions due to the current hook design. 27 | * The renderer does *not* (or rather, can't) keep the references to the parent hooks. 28 | * Consequently it is up to the hooks to ensure that 29 | * **the parent hook is called when the search on the current hook has failed**. 30 | * Doing this incorrectly would give bugs very hard to debug or trace, naturally. 31 | * 32 | * The hook interface provides a convenience method, `delegate`, to simplify this matter: 33 | * Whenever the block hook wants to give a new hook `new_hook` to the closure, 34 | * it should give `&parent.delegate(new_hook)` instead 35 | * which automatically searchs `parent` when the search on `new_hook` fails. 36 | * (It cannot be `&self.delegate(new_hook)` since this won't work for multiple delegations.) 37 | * Also, whenever the block hook wants to delegate the *current* block hook to others, 38 | * it should call the delegated hooks' `run_block_hook` method instead of the direct `block_hook`; 39 | * this ensures that the delegated hook will continue to search on the parent hooks. 40 | */ 41 | 42 | #![macro_escape] 43 | 44 | use gfx::skin::scalar::{Scalar, IntoScalar}; 45 | 46 | /// The hook interface. 47 | pub trait Hook { 48 | /// The scalar hook. The hook should return a scalar value or `None` if the search has failed. 49 | fn scalar_hook<'a>(&'a self, _id: &str) -> Option> { 50 | None 51 | } 52 | 53 | /** 54 | * The block hook. The hook should call `body` with the newly generated hooks, 55 | * which should be either `parent` or `&parent.delegate(other_hook)`, zero or more times. 56 | * `Body` can return `false` to request the hook to stop the iteration. 57 | * The hook should return `true` when the search succeeded, 58 | * even when it didn't actually call the `body` at all. 59 | * 60 | * Do not call `block_hook` methods directly from other `block_hook`s; 61 | * this wrecks the delegation chain. Use `run_block_hook` instead. 62 | */ 63 | fn block_hook(&self, _id: &str, _parent: &Hook, 64 | _body: |newhook: &Hook, alt: &str| -> bool) -> bool { 65 | false 66 | } 67 | 68 | /// Runs the block hook from other block hooks. 69 | /// Note that `body` is now a reference to the closure (easier to call it in this way). 70 | /// Same as `block_hook` but does not wreck the delegation chain. 71 | fn run_block_hook(&self, id: &str, parent: &Hook, body: &mut |&Hook, &str| -> bool) -> bool { 72 | self.block_hook(id, parent, |hook,alt| (*body)(&parent.delegate(hook),alt)) 73 | } 74 | 75 | /// Returns a delegated hook that tries `delegated` first and `self` later. 76 | fn delegate<'a>(&'a self, delegated: &'a Hook) -> Delegate<'a> { 77 | Delegate { base: self, delegated: delegated } 78 | } 79 | 80 | /// Returns a delegated hook that gives `value` for `id` scalar hook first and 81 | /// tries `self` later. 82 | fn add_text<'a>(&'a self, id: &'a str, value: &'a str) -> AddText<'a> { 83 | AddText { base: self, id: id, value: value } 84 | } 85 | } 86 | 87 | impl<'a,T:Hook> Hook for &'a T { 88 | fn scalar_hook<'a>(&'a self, id: &str) -> Option> { 89 | (**self).scalar_hook(id) 90 | } 91 | 92 | fn block_hook(&self, id: &str, parent: &Hook, body: |&Hook, &str| -> bool) -> bool { 93 | (**self).block_hook(id, parent, body) 94 | } 95 | } 96 | 97 | impl Hook for Box { 98 | fn scalar_hook<'a>(&'a self, id: &str) -> Option> { 99 | (**self).scalar_hook(id) 100 | } 101 | 102 | fn block_hook(&self, id: &str, parent: &Hook, body: |&Hook, &str| -> bool) -> bool { 103 | (**self).block_hook(id, parent, body) 104 | } 105 | } 106 | 107 | impl Hook for Option { 108 | fn block_hook(&self, id: &str, parent: &Hook, body: |&Hook, &str| -> bool) -> bool { 109 | match *self { 110 | Some(ref hook) => hook.block_hook(id, parent, body), 111 | None => false 112 | } 113 | } 114 | } 115 | 116 | /// A delegated hook with the order. 117 | pub struct Delegate<'a> { 118 | base: &'a (Hook+'a), 119 | delegated: &'a (Hook+'a), 120 | } 121 | 122 | impl<'a> Hook for Delegate<'a> { 123 | fn scalar_hook<'a>(&'a self, id: &str) -> Option> { 124 | self.delegated.scalar_hook(id) 125 | .or_else(|| self.base.scalar_hook(id)) 126 | } 127 | 128 | fn block_hook(&self, id: &str, parent: &Hook, mut body: |&Hook, &str| -> bool) -> bool { 129 | self.delegated.run_block_hook(id, parent, &mut body) || 130 | self.base.run_block_hook(id, parent, &mut body) 131 | } 132 | } 133 | 134 | /// A delegated hook with a single scalar hook added. 135 | pub struct AddText<'a> { 136 | base: &'a (Hook+'a), 137 | id: &'a str, 138 | value: &'a str, 139 | } 140 | 141 | impl<'a> Hook for AddText<'a> { 142 | fn scalar_hook<'a>(&'a self, id: &str) -> Option> { 143 | if self.id == id { 144 | Some(self.value.into_scalar()) 145 | } else { 146 | self.base.scalar_hook(id) 147 | } 148 | } 149 | 150 | fn block_hook(&self, id: &str, parent: &Hook, mut body: |&Hook, &str| -> bool) -> bool { 151 | self.base.run_block_hook(id, parent, &mut body) 152 | } 153 | } 154 | 155 | macro_rules! define_hooks( 156 | ($(for $ty:ty |$slf:ident, $id:ident, $parent:ident, $body:ident| { 157 | $(delegate $delegate:expr;)* 158 | $(scalar $scalarname:pat => $scalarvalue:expr;)* 159 | $(block $blockname:pat => $blockvalue:expr;)* 160 | })*) => ($( 161 | impl ::gfx::skin::hook::Hook for $ty { 162 | #[allow(unused_variables)] 163 | fn scalar_hook<'a>(&'a self, id: &str) -> Option<::gfx::skin::scalar::Scalar<'a>> { 164 | #[allow(unused_imports)] use gfx::skin::scalar::{AsScalar, IntoScalar}; 165 | type Scalar<'a> = ::gfx::skin::scalar::Scalar<'a>; 166 | 167 | match id { 168 | $($scalarname => { 169 | (|$slf: &'a $ty, $id: &str| Some::>($scalarvalue))(self, id) 170 | })* 171 | _ => { 172 | $(match (|$slf: &'a $ty, $id: &str| $delegate.scalar_hook(id))(self, id) { 173 | Some(v) => { return Some(v); } 174 | None => {} 175 | })* 176 | None 177 | } 178 | } 179 | } 180 | 181 | #[allow(unused_mut, unused_variables)] 182 | fn block_hook(&self, id: &str, parent: &::gfx::skin::hook::Hook, 183 | mut body: |&::gfx::skin::hook::Hook, &str| -> bool) -> bool { 184 | #[allow(unused_imports)] use gfx::skin::scalar::{AsScalar, IntoScalar}; 185 | type HookRef<'a> = &'a (::gfx::skin::hook::Hook+'a); 186 | type Body<'a> = |&::gfx::skin::hook::Hook, &str|: 'a -> bool; 187 | 188 | match id { 189 | $($blockname => { 190 | (|$slf: &$ty, $id: &str, $parent: HookRef, $body: Body| 191 | $blockvalue)(self, id, parent, |hook,alt| body(hook,alt)); 192 | true 193 | })* 194 | _ => { 195 | $(if (|$slf: &$ty, $id: &str, $parent: HookRef, mut $body: Body| 196 | $delegate.run_block_hook($id, $parent, &mut $body)) 197 | (self, id, parent, |hook,alt| body(hook,alt)) { 198 | return true; 199 | })* 200 | false 201 | } 202 | } 203 | } 204 | } 205 | )*) 206 | ) 207 | 208 | -------------------------------------------------------------------------------- /src/format/bms/diag.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md and LICENSE.txt for details. 4 | 5 | //! Diagnostics for BMS format. 6 | 7 | #![allow(non_upper_case_globals)] 8 | 9 | use std::fmt; 10 | 11 | /// The severity of messages. Every error message has one of the severity assigned. 12 | #[deriving(PartialEq,Eq,PartialOrd,Show,Clone)] 13 | pub enum Severity { 14 | /// Various notes. This kind of diagnostics does not affect the game play at all but indicates 15 | /// possible incompatibilities or deprecated features. 16 | Note, 17 | /// Warning message. This kind of diagnostics does not affect the game play a lot but has 18 | /// visible and generally unwanted effects. The caller is recommended to show these messages 19 | /// when the BMS file was played for the first time. 20 | Warning, 21 | /// Fatal error message. This kind of diagnostics means that it's impossible to play this BMS 22 | /// file without massively affecting the game play. The caller is recommended to abort 23 | /// the loading when received this message. 24 | Fatal, 25 | } 26 | 27 | /// Messages for BMS format. 28 | #[deriving(PartialEq,Eq,Clone)] 29 | pub struct BmsMessage { 30 | /// The severity of message. 31 | pub severity: Severity, 32 | /// The internal identifier. 33 | pub id: &'static str, 34 | /// The string representation of the message. 35 | pub message: &'static str, 36 | } 37 | 38 | impl fmt::Show for BmsMessage { 39 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 40 | write!(f, "{}", self.message) 41 | } 42 | } 43 | 44 | pub static BmsHasNegativeInitBPM: BmsMessage = BmsMessage { 45 | severity: Severity::Fatal, 46 | id: "neg-init-bpm", 47 | message: "Initial #BPM cannot be negative. This line will be ignored.", 48 | }; 49 | 50 | pub static BmsHasZeroInitBPM: BmsMessage = BmsMessage { 51 | severity: Severity::Fatal, 52 | id: "zero-init-bpm", 53 | message: "Initial #BPM cannot be zero. This line will be ignored.", 54 | }; 55 | 56 | pub static BmsHasNegativeSTOPDuration: BmsMessage = BmsMessage { 57 | severity: Severity::Fatal, 58 | id: "neg-stop-duration", 59 | message: "#STOP duration cannot be negative. This #STOP will be ignored.", 60 | }; 61 | 62 | pub static BmsHasNegativeSTPDuration: BmsMessage = BmsMessage { 63 | severity: Severity::Fatal, 64 | id: "neg-stp-duration", 65 | message: "#STP duration cannot be negative. This line will be ignored.", 66 | }; 67 | 68 | pub static BmsHasNoTITLE: BmsMessage = BmsMessage { 69 | severity: Severity::Warning, 70 | id: "no-title", 71 | message: "#TITLE is missing.", 72 | }; 73 | 74 | pub static BmsHasEmptyTITLE: BmsMessage = BmsMessage { 75 | severity: Severity::Warning, 76 | id: "empty-title", 77 | message: "#TITLE is empty.", 78 | }; 79 | 80 | pub static BmsHasMultipleTITLEs: BmsMessage = BmsMessage { 81 | severity: Severity::Warning, 82 | id: "multiple-title", 83 | message: "There are multiple #TITLE commands. Only the last such line will be used.", 84 | }; 85 | 86 | pub static BmsUsesCouplePlay: BmsMessage = BmsMessage { 87 | severity: Severity::Warning, 88 | id: "couple-play", 89 | message: "Support for Couple Play (#PLAYER 2) is limited.", 90 | }; 91 | 92 | pub static BmsUsesBattlePlay: BmsMessage = BmsMessage { 93 | severity: Severity::Warning, 94 | id: "battle-play", 95 | message: "Battle Play (#PLAYER 4) is not supported, and will be treated as Single Play.", 96 | }; 97 | 98 | pub static BmsHasInvalidLNTYPE: BmsMessage = BmsMessage { 99 | severity: Severity::Warning, 100 | id: "invalid-lntype", 101 | message: "Invalid #LNTYPE value will be ignored.", 102 | }; 103 | 104 | pub static BmsHasZeroLNOBJ: BmsMessage = BmsMessage { 105 | severity: Severity::Warning, 106 | id: "zero-lnobj", 107 | message: "#LNOBJ 00 is invalid and will be ignored.", 108 | }; 109 | 110 | pub static BmsHasMultipleLNOBJs: BmsMessage = BmsMessage { 111 | severity: Severity::Warning, 112 | id: "multiple-lnobj", 113 | message: "There are multiple #LNOBJ commands. Only the last such line will be used.", 114 | }; 115 | 116 | pub static BmsHasUnimplementedFlow: BmsMessage = BmsMessage { 117 | severity: Severity::Warning, 118 | id: "unimplemented-flow", 119 | message: "#SWITCH and related flow commands are not yet implemented and may malfunction.", 120 | }; 121 | 122 | pub static BmsUsesLegacyEncoding: BmsMessage = BmsMessage { 123 | severity: Severity::Note, 124 | id: "legacy-encoding", 125 | message: "The file is encoded in the legacy CJK encodings. Their continued use is discouraged.", 126 | }; 127 | 128 | pub static BmsHasFullWidthSharp: BmsMessage = BmsMessage { 129 | severity: Severity::Note, 130 | id: "full-width-sharp", 131 | message: "# should be a half-width letter for the compatibility.", 132 | }; 133 | 134 | pub static BmsHasOneDigitAlphanumericKey: BmsMessage = BmsMessage { 135 | severity: Severity::Note, 136 | id: "one-digit-key", 137 | message: "One-digit alphanumeric key is assumed to be prepended by 0 digit.", 138 | }; 139 | 140 | pub static BmsHasNoARTIST: BmsMessage = BmsMessage { 141 | severity: Severity::Note, 142 | id: "no-artist", 143 | message: "#ARTIST is missing.", 144 | }; 145 | 146 | pub static BmsHasEmptyARTIST: BmsMessage = BmsMessage { 147 | severity: Severity::Note, 148 | id: "empty-artist", 149 | message: "#ARTIST is empty.", 150 | }; 151 | 152 | pub static BmsHasMultipleARTISTs: BmsMessage = BmsMessage { 153 | severity: Severity::Note, 154 | id: "multiple-artist", 155 | message: "There are multiple #ARTIST commands. Only the last such line will be used.", 156 | }; 157 | 158 | pub static BmsHasNoGENRE: BmsMessage = BmsMessage { 159 | severity: Severity::Note, 160 | id: "no-genre", 161 | message: "#GENRE is missing.", 162 | }; 163 | 164 | pub static BmsHasEmptyGENRE: BmsMessage = BmsMessage { 165 | severity: Severity::Note, 166 | id: "empty-genre", 167 | message: "#GENRE is empty.", 168 | }; 169 | 170 | pub static BmsHasMultipleGENREs: BmsMessage = BmsMessage { 171 | severity: Severity::Note, 172 | id: "multiple-genre", 173 | message: "There are multiple #GENRE commands. Only the last such line will be used.", 174 | }; 175 | 176 | pub static BmsHasGENLE: BmsMessage = BmsMessage { 177 | severity: Severity::Note, 178 | id: "genle", 179 | message: "#GENLE [sic] will be interpreted as #GENRE.", 180 | }; 181 | 182 | pub static BmsHasEmptyPath: BmsMessage = BmsMessage { 183 | severity: Severity::Note, 184 | id: "empty-path", 185 | message: "Empty path will be ignored.", 186 | }; 187 | 188 | pub static BmsHasInvalidPLAYER: BmsMessage = BmsMessage { 189 | severity: Severity::Note, 190 | id: "invalid-player", 191 | message: "Invalid #PLAYER value will be ignored.", 192 | }; 193 | 194 | pub static BmsHasNegativePLAYLEVEL: BmsMessage = BmsMessage { 195 | severity: Severity::Note, 196 | id: "neg-playlevel", 197 | message: "#PLAYLEVEL should be non-negative for the compatibility.", 198 | }; 199 | 200 | pub static BmsHasDIFFICULTYOutOfRange: BmsMessage = BmsMessage { 201 | severity: Severity::Note, 202 | id: "difficulty-out-of-range", 203 | message: "#DIFFICULTY should be between 1 and 5 for the compatibility.", 204 | }; 205 | 206 | pub static BmsHasEXBPM: BmsMessage = BmsMessage { 207 | severity: Severity::Note, 208 | id: "exbpm", 209 | message: "#EXBPMxx is a temporary measure, you should use #BPMxx instead.", 210 | }; 211 | 212 | pub static BmsHasNonpositiveBPM: BmsMessage = BmsMessage { 213 | severity: Severity::Note, 214 | id: "nonpos-bpm", 215 | message: "Non-positive BPM is not portable and its use is discouraged.", 216 | }; 217 | pub static BmsUsesLNTYPE2: BmsMessage = BmsMessage { 218 | severity: Severity::Note, 219 | id: "lntype2", 220 | message: "#LNTYPE 2 is deprecated, you should use #LNTYPE 1 (implied) or #LNOBJ instead.", 221 | }; 222 | 223 | pub static BmsHasUnknownWAVCMD: BmsMessage = BmsMessage { 224 | severity: Severity::Note, 225 | id: "unknown-wavcmd", 226 | message: "Invalid #WAVCMD command will be ignored.", 227 | }; 228 | 229 | pub static BmsHasSONG: BmsMessage = BmsMessage { 230 | severity: Severity::Note, 231 | id: "song", 232 | message: "#SONG is deprecated, you should use #TEXT instead.", 233 | }; 234 | 235 | pub static BmsHasRONDAM: BmsMessage = BmsMessage { 236 | severity: Severity::Note, 237 | id: "rondam", 238 | message: "#RONDAM [sic] will be interpreted as #RANDOM.", 239 | }; 240 | 241 | pub static BmsHasRANDOMWithoutWhitespace: BmsMessage = BmsMessage { 242 | severity: Severity::Note, 243 | id: "rondam-no-ws", 244 | message: "#RANDOM should be followed by one or more whitespace.", 245 | }; 246 | 247 | pub static BmsHasIFWithoutWhitespace: BmsMessage = BmsMessage { 248 | severity: Severity::Note, 249 | id: "if-no-ws", 250 | message: "#IF should be followed by one or more whitespace.", 251 | }; 252 | 253 | pub static BmsHasIFEND: BmsMessage = BmsMessage { 254 | severity: Severity::Note, 255 | id: "ifend", 256 | message: "#IFEND [sic] will be interpreted as #ENDIF.", 257 | }; 258 | 259 | pub static BmsHasENDNotFollowedByIF: BmsMessage = BmsMessage { 260 | severity: Severity::Note, 261 | id: "end-without-if", 262 | message: "#END not followed by IF will be interpreted as #ENDIF.", 263 | }; 264 | 265 | -------------------------------------------------------------------------------- /src/gfx/skin/scalar.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md for details. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or 6 | // the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! In-skin scalar value. 10 | 11 | use std::fmt; 12 | use std::rc::Rc; 13 | use std::str::CowString; 14 | 15 | use gfx::color::{Color, RGB, RGBA}; 16 | use gfx::ratio_num::RatioNum; 17 | use gfx::gl::Texture2D; 18 | 19 | /// The image reference in the scalar value. 20 | pub enum ImageSource<'a> { 21 | /** 22 | * A reference to the texture, contained in the `Rc` box. 23 | * 24 | * `Rc` is required because the renderer tries to delay the draw calls as long as possible, 25 | * so the reference to the texture may be kept indefinitely. 26 | * The renderer does try not to touch the `Rc` box itself until strictly required, 27 | * thus it requires the *reference* to the `Rc` box containing the texture. 28 | */ 29 | Texture(&'a Rc), 30 | /// A reference to the image file. 31 | /// The renderer is free to cache the resulting texture per the path. 32 | Path(Path), 33 | } 34 | 35 | impl<'a> Clone for ImageSource<'a> { 36 | fn clone(&self) -> ImageSource<'a> { 37 | match *self { 38 | ImageSource::Texture(tex) => ImageSource::Texture(tex), 39 | ImageSource::Path(ref path) => ImageSource::Path(path.clone()), 40 | } 41 | } 42 | } 43 | 44 | /// The clipping rectangle. 45 | #[deriving(Clone, Show)] 46 | pub struct ImageClip { 47 | pub x: RatioNum, 48 | pub y: RatioNum, 49 | pub w: RatioNum, 50 | pub h: RatioNum, 51 | } 52 | 53 | impl ImageClip { 54 | /// Returns a new, full-screen clipping rectangle. 55 | pub fn new() -> ImageClip { 56 | ImageClip { x: RatioNum::zero(), w: RatioNum::one(), 57 | y: RatioNum::zero(), h: RatioNum::one() } 58 | } 59 | 60 | /// Substitutes the current base rectangle with another clipping rectangle: 61 | /// 62 | /// ```notrust 63 | /// +---------------+ 64 | /// | +-------+ | 65 | /// | | | <-------- self 66 | /// | +-------+ | <-- other 67 | /// | | 68 | /// +---------------+ 69 | /// ``` 70 | pub fn subst(&self, other: &ImageClip) -> ImageClip { 71 | let x = other.x + self.x.subst(&other.w); 72 | let y = other.y + self.y.subst(&other.h); 73 | let w = self.w.subst(&other.w); 74 | let h = self.h.subst(&other.h); 75 | ImageClip { x: x, y: y, w: w, h: h } 76 | } 77 | } 78 | 79 | /// The scalar value. 80 | pub enum Scalar<'a> { 81 | /// An owned string. Analogous to `std::str::CowString`. 82 | OwnedStr(String), 83 | /// A borrowed string slice. Analogous to `std::str::CowString`. 84 | BorrowedStr(&'a str), 85 | /// An image reference and the clipping rectangle in pixels. 86 | Image(ImageSource<'a>, ImageClip), 87 | /// A signed integer. 88 | Int(int), 89 | /// An unsigned integer. 90 | Uint(uint), 91 | /// A 32-bit floating point number. 92 | F32(f32), 93 | /// A 64-bit floating point number. 94 | F64(f64), 95 | /// A color. 96 | Color(Color), 97 | } 98 | 99 | impl<'a> Scalar<'a> { 100 | /// Extracts the string slice if any. 101 | pub fn as_slice(&'a self) -> Option<&'a str> { 102 | match *self { 103 | Scalar::OwnedStr(ref s) => Some(s[]), 104 | Scalar::BorrowedStr(s) => Some(s), 105 | _ => None, 106 | } 107 | } 108 | 109 | /// Extracts the image reference if any. 110 | pub fn as_image_source(&'a self) -> Option<&'a ImageSource<'a>> { 111 | match *self { 112 | Scalar::Image(ref src, _clip) => Some(src), 113 | _ => None, 114 | } 115 | } 116 | } 117 | 118 | /// A trait for `as_scalar` convenience method. 119 | pub trait AsScalar<'a> { 120 | /// Converts the value to the scalar value with no copying. 121 | fn as_scalar(&'a self) -> Scalar<'a>; 122 | } 123 | 124 | /// A trait for `into_scalar` convenience method. 125 | pub trait IntoScalar<'a> { 126 | /// Converts the value to the scalar value while giving the ownership up. 127 | fn into_scalar(self) -> Scalar<'a>; 128 | } 129 | 130 | impl<'a> IntoScalar<'a> for Scalar<'a> { 131 | #[inline] fn into_scalar(self) -> Scalar<'a> { self } 132 | } 133 | 134 | impl<'a> IntoScalar<'a> for &'a str { 135 | #[inline] fn into_scalar(self) -> Scalar<'a> { Scalar::BorrowedStr(self) } 136 | } 137 | 138 | impl<'a> AsScalar<'a> for String { 139 | #[inline] fn as_scalar(&'a self) -> Scalar<'a> { Scalar::BorrowedStr(self[]) } 140 | } 141 | 142 | impl IntoScalar<'static> for String { 143 | #[inline] fn into_scalar(self) -> Scalar<'static> { Scalar::OwnedStr(self) } 144 | } 145 | 146 | impl<'a> AsScalar<'a> for Rc { 147 | #[inline] 148 | fn as_scalar(&'a self) -> Scalar<'a> { 149 | Scalar::Image(ImageSource::Texture(self), ImageClip::new()) 150 | } 151 | } 152 | 153 | macro_rules! scalar_conv_impls( 154 | ($t:ty -> |$v:ident| $e:expr) => ( 155 | impl<'a> AsScalar<'a> for $t { 156 | #[inline] fn as_scalar(&'a self) -> Scalar<'a> { let $v: $t = *self; $e } 157 | } 158 | impl IntoScalar<'static> for $t { 159 | #[inline] fn into_scalar(self) -> Scalar<'static> { let $v: $t = self; $e } 160 | } 161 | ) 162 | ) 163 | 164 | scalar_conv_impls!(int -> |v| Scalar::Int(v)) 165 | scalar_conv_impls!(i8 -> |v| Scalar::Int(v as int)) 166 | scalar_conv_impls!(i16 -> |v| Scalar::Int(v as int)) 167 | scalar_conv_impls!(i64 -> |v| Scalar::Int(v as int)) 168 | scalar_conv_impls!(i32 -> |v| Scalar::Int(v as int)) 169 | scalar_conv_impls!(uint -> |v| Scalar::Uint(v)) 170 | scalar_conv_impls!(u8 -> |v| Scalar::Uint(v as uint)) 171 | scalar_conv_impls!(u16 -> |v| Scalar::Uint(v as uint)) 172 | scalar_conv_impls!(u64 -> |v| Scalar::Uint(v as uint)) 173 | scalar_conv_impls!(u32 -> |v| Scalar::Uint(v as uint)) 174 | scalar_conv_impls!(f32 -> |v| Scalar::F32(v)) 175 | scalar_conv_impls!(f64 -> |v| Scalar::F64(v)) 176 | scalar_conv_impls!(Color -> |v| Scalar::Color(v)) 177 | 178 | impl<'a> fmt::Show for ImageSource<'a> { 179 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 180 | match *self { 181 | ImageSource::Texture(tex) => { 182 | let tex = tex.deref(); 183 | write!(f, "", tex.width, tex.height) 184 | }, 185 | ImageSource::Path(ref path) => write!(f, "", path.display()), 186 | } 187 | } 188 | } 189 | 190 | impl<'a> fmt::Show for Scalar<'a> { 191 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 192 | match *self { 193 | Scalar::OwnedStr(ref s) => s.fmt(f), 194 | Scalar::BorrowedStr(s) => s.fmt(f), 195 | Scalar::Image(ref src, ref clip) => write!(f, "Scalar::Image({}, {})", *src, *clip), 196 | Scalar::Int(v) => v.fmt(f), 197 | Scalar::Uint(v) => v.fmt(f), 198 | Scalar::F32(v) => v.fmt(f), 199 | Scalar::F64(v) => v.fmt(f), 200 | Scalar::Color(RGB(r, g, b)) => write!(f, "#{:02x}{:02x}{:02x}", r, g, b), 201 | Scalar::Color(RGBA(r, g, b, a)) => write!(f, "#{:02x}{:02x}{:02x}{:02x}", r, g, b, a), 202 | } 203 | } 204 | } 205 | 206 | impl<'a> IntoCow<'a, String, str> for Scalar<'a> { 207 | fn into_cow(self) -> CowString<'a> { 208 | match self { 209 | Scalar::OwnedStr(s) => s.into_cow(), 210 | Scalar::BorrowedStr(s) => s.into_cow(), 211 | scalar => scalar.to_string().into_cow(), 212 | } 213 | } 214 | } 215 | 216 | macro_rules! scalar_to_prim_impl( 217 | ($($f:ident -> $t:ty);*) => ( 218 | impl<'a> ToPrimitive for Scalar<'a> { 219 | $( 220 | fn $f(&self) -> Option<$t> { 221 | match *self { 222 | Scalar::OwnedStr(..) => None, 223 | Scalar::BorrowedStr(..) => None, 224 | Scalar::Image(..) => None, 225 | Scalar::Int(v) => v.$f(), 226 | Scalar::Uint(v) => v.$f(), 227 | Scalar::F32(v) => v.$f(), 228 | Scalar::F64(v) => v.$f(), 229 | Scalar::Color(..) => None, 230 | } 231 | } 232 | )* 233 | } 234 | ) 235 | ) 236 | 237 | scalar_to_prim_impl!( 238 | to_int -> int; to_i8 -> i8; to_i16 -> i16; to_i32 -> i32; to_i64 -> i64; 239 | to_uint -> uint; to_u8 -> u8; to_u16 -> u16; to_u32 -> u32; to_u64 -> u64; 240 | to_f32 -> f32; to_f64 -> f64 241 | ) 242 | 243 | impl<'a> Clone for Scalar<'a> { 244 | fn clone(&self) -> Scalar<'a> { 245 | match *self { 246 | Scalar::OwnedStr(ref s) => Scalar::OwnedStr(s.to_string()), 247 | Scalar::BorrowedStr(s) => Scalar::BorrowedStr(s), 248 | Scalar::Image(ref src, ref clip) => Scalar::Image(src.clone(), clip.clone()), 249 | Scalar::Int(v) => Scalar::Int(v), 250 | Scalar::Uint(v) => Scalar::Uint(v), 251 | Scalar::F32(v) => Scalar::F32(v), 252 | Scalar::F64(v) => Scalar::F64(v), 253 | Scalar::Color(v) => Scalar::Color(v), 254 | } 255 | } 256 | } 257 | 258 | -------------------------------------------------------------------------------- /src/format/bms/preproc.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md and LICENSE.txt for details. 4 | 5 | //! BMS preprocessor. 6 | 7 | use std::fmt; 8 | use std::rand::Rng; 9 | 10 | use format::bms::diag; 11 | use format::bms::diag::BmsMessage; 12 | 13 | /// Represents one line of BMS command that may affect the control flow. 14 | #[deriving(PartialEq,Clone)] 15 | pub enum BmsFlow { 16 | RANDOM(int), // #RANDOM 17 | SETRANDOM(int), // #SETRANDOM 18 | ENDRANDOM, // #ENDRANDOM 19 | IF(int), // #IF 20 | ELSEIF(int), // #ELSEIF 21 | ELSE, // #ELSE 22 | ENDIF, // #ENDIF 23 | SWITCH(int), // #SWITCH 24 | SETSWITCH(int), // #SETSWITCH 25 | ENDSW, // #ENDSW 26 | CASE(int), // #CASE 27 | SKIP, // #SKIP 28 | DEF, // #DEF 29 | } 30 | 31 | impl fmt::Show for BmsFlow { 32 | /// Returns a reconstructed line for given BMS flow command. 33 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 34 | match *self { 35 | BmsFlow::RANDOM(val) => write!(f, "#RANDOM {}", val), 36 | BmsFlow::SETRANDOM(val) => write!(f, "#SETRANDOM {}", val), 37 | BmsFlow::ENDRANDOM => write!(f, "#ENDRANDOM"), 38 | BmsFlow::IF(val) => write!(f, "#IF {}", val), 39 | BmsFlow::ELSEIF(val) => write!(f, "#ELSEIF {}", val), 40 | BmsFlow::ELSE => write!(f, "#ELSE"), 41 | BmsFlow::ENDIF => write!(f, "#ENDIF"), 42 | BmsFlow::SWITCH(val) => write!(f, "#SWITCH {}", val), 43 | BmsFlow::SETSWITCH(val) => write!(f, "#SETSWITCH {}", val), 44 | BmsFlow::ENDSW => write!(f, "#ENDSW"), 45 | BmsFlow::CASE(val) => write!(f, "#CASE {}", val), 46 | BmsFlow::SKIP => write!(f, "#SKIP"), 47 | BmsFlow::DEF => write!(f, "#DEF"), 48 | } 49 | } 50 | } 51 | 52 | /// The state of the block, for determining which lines should be processed. 53 | #[deriving(PartialEq,Eq)] 54 | enum BlockState { 55 | /// Not contained in the #IF block. 56 | Outside, 57 | /// Active. 58 | Process, 59 | /// Inactive, but (for the purpose of #IF/#ELSEIF/#ELSE/#ENDIF structure) can move to 60 | /// `Process` state when matching clause appears. 61 | Ignore, 62 | /// Inactive and won't be processed until the end of block. 63 | NoFurther 64 | } 65 | 66 | impl BlockState { 67 | /// Returns true if lines should be ignored in the current block given that the parent 68 | /// block was active. 69 | fn inactive(self) -> bool { 70 | match self { 71 | BlockState::Outside | BlockState::Process => false, 72 | BlockState::Ignore | BlockState::NoFurther => true 73 | } 74 | } 75 | } 76 | 77 | /** 78 | * Block information. The parser keeps a list of nested blocks and determines if 79 | * a particular line should be processed or not. 80 | * 81 | * Sonorous actually recognizes only one kind of blocks, starting with #RANDOM or 82 | * #SETRANDOM and ending with #ENDRANDOM or #END(IF) outside an #IF block. An #IF block is 83 | * a state within #RANDOM, so it follows that #RANDOM/#SETRANDOM blocks can nest but #IF 84 | * can't nest unless its direct parent is #RANDOM/#SETRANDOM. 85 | */ 86 | #[deriving(PartialEq,Eq)] 87 | struct Block { 88 | /// A generated value if any. It can be `None` if this block is the topmost one (which 89 | /// is actually not a block but rather a sentinel) or the last `#RANDOM` or `#SETRANDOM` 90 | /// command was invalid, and #IF in that case will always evaluates to false. 91 | val: Option, 92 | /// The state of the block. 93 | state: BlockState, 94 | /// True if the parent block is already ignored so that this block should be ignored 95 | /// no matter what `state` is. 96 | skip: bool 97 | } 98 | 99 | /// A generic BMS preprocessor. `T` is normally a BMS command, but there is no restriction. 100 | pub struct Preprocessor<'r, T, R:'r> { 101 | /// The current block informations. 102 | blocks: Vec, 103 | /// Random number generator. 104 | r: &'r mut R, 105 | } 106 | 107 | impl<'r,T:Send+Clone,R:Rng> Preprocessor<'r,T,R> { 108 | /// Creates a new preprocessor with given RNG. 109 | pub fn new(r: &'r mut R) -> Preprocessor<'r,T,R> { 110 | let blocks = vec![Block { val: None, state: BlockState::Outside, skip: false }]; 111 | Preprocessor { blocks: blocks, r: r } 112 | } 113 | 114 | /// Returns true if any command which appears at this position should be ignored. 115 | pub fn inactive(&self) -> bool { 116 | let last = self.blocks.last().unwrap(); 117 | last.skip || last.state.inactive() 118 | } 119 | 120 | /// Adds the non-flow command (or any appropriate data) into the preprocessor. 121 | /// `messages` will have zero or more messages inserted. 122 | /// `result` will have zero or more preprocessed commands (or any appropriate data) inserted. 123 | pub fn feed_other(&mut self, cmd: T, _messages: &mut Vec, result: &mut Vec) { 124 | if !self.inactive() { 125 | result.push(cmd); 126 | } 127 | } 128 | 129 | /// Adds the flow command into the preprocessor. 130 | /// `messages` will have zero or more messages inserted. 131 | /// `result` will have zero or more preprocessed commands (or any appropriate data) inserted. 132 | pub fn feed_flow(&mut self, _lineno: Option, flow: &BmsFlow, 133 | messages: &mut Vec, _result: &mut Vec) { 134 | let inactive = self.inactive(); 135 | match *flow { 136 | BmsFlow::RANDOM(val) | BmsFlow::SETRANDOM(val) => { 137 | let val = if val <= 0 {None} else {Some(val)}; 138 | let setrandom = match *flow { BmsFlow::SETRANDOM(..) => true, _ => false }; 139 | 140 | // do not generate a random value if the entire block is skipped (but it 141 | // still marks the start of block) 142 | let generated = val.and_then(|val| { 143 | if setrandom { 144 | Some(val) 145 | } else if !inactive { 146 | Some(self.r.gen_range(1, val + 1)) 147 | } else { 148 | None 149 | } 150 | }); 151 | self.blocks.push(Block { val: generated, state: BlockState::Outside, 152 | skip: inactive }); 153 | } 154 | BmsFlow::ENDRANDOM => { 155 | if self.blocks.len() > 1 { self.blocks.pop(); } 156 | } 157 | BmsFlow::IF(val) | BmsFlow::ELSEIF(val) => { 158 | let val = if val <= 0 {None} else {Some(val)}; 159 | let haspriorelse = match *flow { BmsFlow::ELSEIF(..) => true, _ => false }; 160 | 161 | let last = self.blocks.last_mut().unwrap(); 162 | last.state = 163 | if (!haspriorelse && !last.state.inactive()) || 164 | last.state == BlockState::Ignore { 165 | if val.is_none() || val != last.val {BlockState::Ignore} 166 | else {BlockState::Process} 167 | } else { 168 | BlockState::NoFurther 169 | }; 170 | } 171 | BmsFlow::ELSE => { 172 | let last = self.blocks.last_mut().unwrap(); 173 | last.state = if last.state == BlockState::Ignore {BlockState::Process} 174 | else {BlockState::NoFurther}; 175 | } 176 | BmsFlow::ENDIF => { 177 | for &idx in self.blocks.iter() 178 | .rposition(|&i| i.state != BlockState::Outside).iter() { 179 | if idx > 0 { self.blocks.truncate(idx + 1); } 180 | } 181 | 182 | self.blocks.last_mut().unwrap().state = BlockState::Outside; 183 | } 184 | BmsFlow::SWITCH(..) | BmsFlow::SETSWITCH(..) | BmsFlow::ENDSW | 185 | BmsFlow::CASE(..) | BmsFlow::SKIP | BmsFlow::DEF => { 186 | messages.push(diag::BmsHasUnimplementedFlow); 187 | } 188 | } 189 | } 190 | 191 | /// Terminates the preprocessor. 192 | /// `messages` will have zero or more messages inserted. 193 | /// `result` will have zero or more preprocessed commands (or any appropriate data) inserted. 194 | pub fn finish(&mut self, _messages: &mut Vec, _result: &mut Vec) { 195 | } 196 | } 197 | 198 | #[cfg(test)] 199 | mod tests { 200 | use std::rand::task_rng; 201 | use super::Preprocessor; 202 | 203 | macro_rules! with_pp( 204 | (|$pp:ident| $blk:expr) => ({ 205 | let mut r = task_rng(); 206 | let mut $pp = Preprocessor::new(&mut r); 207 | $blk; 208 | }) 209 | ) 210 | 211 | #[test] 212 | fn test_no_flow() { 213 | with_pp!(|pp| { 214 | let mut messages = Vec::new(); 215 | let mut out = Vec::new(); 216 | pp.feed_other(42u, &mut messages, &mut out); 217 | assert!(messages[] == []); 218 | assert!(out[] == [42u]); 219 | messages.clear(); 220 | out.clear(); 221 | pp.finish(&mut messages, &mut out); 222 | assert!(messages[] == []); 223 | assert!(out[] == []); 224 | }) 225 | } 226 | 227 | // TODO add more tests 228 | } 229 | 230 | -------------------------------------------------------------------------------- /src/util/lex.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md for details. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or 6 | // the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! Lexical analysis utilities. 10 | 11 | #![macro_escape] 12 | 13 | /// A version of `std::from_str::FromStr` which parses a prefix of the input and 14 | /// returns a remaining portion of the input as well. 15 | // 16 | // Rust: `std::num::from_str_bytes_common` does not recognize a number followed 17 | // by garbage, so we need to parse it ourselves. 18 | pub trait FromStrPrefix { 19 | /// Returns a parsed value and remaining string slice if possible. 20 | fn from_str_prefix<'a>(s: &'a str) -> Option<(Self, &'a str)>; 21 | } 22 | 23 | /// A convenience function that invokes `FromStrPrefix::from_str_prefix`. 24 | pub fn from_str_prefix<'a, T:FromStrPrefix>(s: &'a str) -> Option<(T, &'a str)> { 25 | FromStrPrefix::from_str_prefix(s) 26 | } 27 | 28 | /// Implementations for `FromStrPrefix`. This avoids exporting internal macros. 29 | mod from_str_prefix_impls { 30 | use super::FromStrPrefix; 31 | 32 | /// Returns a length of the longest prefix of given string, 33 | /// which `from_str::` would in general accept without a failure, if any. 34 | fn scan_uint(s: &str) -> Option { 35 | match s.find(|c| !('0' <= c && c <= '9')) { 36 | Some(first) if first > 0u => Some(first), 37 | None if s.len() > 0u => Some(s.len()), 38 | _ => None 39 | } 40 | } 41 | 42 | /// Returns a length of the longest prefix of given string, 43 | /// which `from_str::` would in general accept without a failure, if any. 44 | fn scan_int(s: &str) -> Option { 45 | match s.slice_shift_char() { 46 | Some(('-', s_)) | Some(('+', s_)) => scan_uint(s_).map(|pos| pos + 1u), 47 | _ => scan_uint(s) 48 | } 49 | } 50 | 51 | /// Returns a length of the longest prefix of given string, 52 | /// which `from_str::` (and so on) would in general accept without a failure, if any. 53 | fn scan_float(s: &str) -> Option { 54 | scan_int(s).and_then(|pos| { 55 | // scan `.` followed by digits if any 56 | match s[pos..].slice_shift_char() { 57 | Some(('.', s_)) => scan_uint(s_).map(|pos2| pos + 1u + pos2), 58 | _ => Some(pos) 59 | } 60 | }).and_then(|pos| { 61 | // scan `e` or `E` followed by an optional sign and digits if any 62 | match s[pos..].slice_shift_char() { 63 | Some(('e', s_)) | Some(('E', s_)) => scan_int(s_).map(|pos2| pos + 1u + pos2), 64 | _ => Some(pos) 65 | } 66 | }) 67 | } 68 | 69 | macro_rules! from_str_prefix_impls( 70 | ($($scan:ident then $t:ty;)*) => ( 71 | $( 72 | impl FromStrPrefix for $t { 73 | fn from_str_prefix<'a>(s: &'a str) -> Option<($t, &'a str)> { 74 | $scan(s).and_then(|pos| { 75 | from_str::<$t>(s[..pos]).map(|v| (v, s[pos..])) 76 | }) 77 | } 78 | } 79 | )* 80 | ) 81 | ) 82 | 83 | from_str_prefix_impls! { 84 | scan_int then int; 85 | scan_int then i8; 86 | scan_int then i16; 87 | scan_int then i32; 88 | scan_int then i64; 89 | scan_uint then uint; 90 | scan_uint then u8; 91 | scan_uint then u16; 92 | scan_uint then u32; 93 | scan_uint then u64; 94 | scan_float then f32; 95 | scan_float then f64; 96 | } 97 | 98 | impl FromStrPrefix for char { 99 | fn from_str_prefix<'a>(s: &'a str) -> Option<(char, &'a str)> { 100 | s.slice_shift_char() 101 | } 102 | } 103 | } 104 | 105 | /// A trait which provides `prefix_shifted` method. Similar to `str::starts_with`, but with 106 | /// swapped `self` and argument. 107 | pub trait ShiftablePrefix { 108 | /// When given string starts with `self`, returns a slice of that string excluding that prefix. 109 | /// Otherwise returns `None`. 110 | fn prefix_shifted<'r>(&self, s: &'r str) -> Option<&'r str>; 111 | } 112 | 113 | /// A convenience function that invokes `ShiftablePrefix::prefix_shifted`. 114 | pub fn prefix_shifted<'a, T:ShiftablePrefix>(s: &'a str, prefix: T) -> Option<&'a str> { 115 | prefix.prefix_shifted(s) 116 | } 117 | 118 | impl ShiftablePrefix for char { 119 | fn prefix_shifted<'r>(&self, s: &'r str) -> Option<&'r str> { 120 | match s.slice_shift_char() { 121 | Some((c, s_)) if c == *self => Some(s_), 122 | _ => None, 123 | } 124 | } 125 | } 126 | 127 | impl<'r> ShiftablePrefix for &'r str { 128 | fn prefix_shifted<'r>(&self, s: &'r str) -> Option<&'r str> { 129 | if s.starts_with(*self) { 130 | Some(s[self.len()..]) 131 | } else { 132 | None 133 | } 134 | } 135 | } 136 | 137 | /** 138 | * A lexer barely powerful enough to parse BMS format. Comparable to C's `sscanf`. 139 | * 140 | * `lex!(e; fmt1, fmt2, ..., fmtN)` returns an expression that evaluates to true if and only if 141 | * all format specification is consumed. The format specification (analogous to `sscanf`'s 142 | * `%`-string) is as follows: 143 | * 144 | * - `ws`: Consumes one or more whitespace. 145 | * - `ws*`: Consumes zero or more whitespace. 146 | * - `int [-> e2]` and so on: Any type implementing the `FromStrPrefix` trait can be used. 147 | * Optionally saves the parsed value to `e2`. The default implementations includes all integers, 148 | * floating point types and `char`. 149 | * - `str [-> e2]`: Consumes a remaining input as a string and optionally saves it to `e2`. 150 | * The string is at least one character long. Implies `!`. It can be followed by `ws*` which 151 | * makes the string right-trimmed. 152 | * - `str* [-> e2]`: Same as above but the string can be empty. 153 | * - `!`: Ensures that the entire string has been consumed. Should be the last format 154 | * specification. 155 | * - `lit "foo"`, `lit '.'` etc.: A literal string or literal character. 156 | * 157 | * Whitespaces are only trimmed when `ws` or `ws*` specification is used. 158 | * Therefore `char`, for example, can read a whitespace when not prepended with `ws` or `ws*`. 159 | */ 160 | // Rust: - it is desirable to have a matcher only accepts an integer literal or string literal, 161 | // not a generic expression. 162 | // - it would be more useful to generate bindings for parsed result. this is related to 163 | // many issues in general. 164 | // - could we elide a `lit` prefix somehow? 165 | macro_rules! lex( 166 | ($e:expr; ) => (true); 167 | ($e:expr; !) => ($e.is_empty()); 168 | 169 | ($e:expr; str -> $dst:expr, ws*, $($tail:tt)*) => ({ 170 | let _line: &str = $e; 171 | if !_line.is_empty() { 172 | $dst = _line.trim_right(); 173 | lex!(""; $($tail)*) // optimization! 174 | } else { 175 | false 176 | } 177 | }); 178 | ($e:expr; str -> $dst:expr, $($tail:tt)*) => ({ 179 | let _line: &str = $e; 180 | if !_line.is_empty() { 181 | $dst = _line[]; 182 | lex!(""; $($tail)*) // optimization! 183 | } else { 184 | false 185 | } 186 | }); 187 | ($e:expr; str* -> $dst:expr, ws*, $($tail:tt)*) => ({ 188 | let _line: &str = $e; 189 | $dst = _line.trim_right(); 190 | lex!(""; $($tail)*) // optimization! 191 | }); 192 | ($e:expr; str* -> $dst:expr, $($tail:tt)*) => ({ 193 | let _line: &str = $e; 194 | $dst = _line[]; 195 | lex!(""; $($tail)*) // optimization! 196 | }); 197 | ($e:expr; $t:ty -> $dst:expr, $($tail:tt)*) => ({ 198 | let _line: &str = $e; 199 | ::util::lex::from_str_prefix::<$t>(_line).map_or(false, |(_value, _line)| { 200 | $dst = _value; 201 | lex!(_line; $($tail)*) 202 | }) 203 | }); 204 | 205 | ($e:expr; ws, $($tail:tt)*) => ({ 206 | let _line: &str = $e; 207 | if !_line.is_empty() && _line.char_at(0).is_whitespace() { 208 | lex!(_line.trim_left(); $($tail)*) 209 | } else { 210 | false 211 | } 212 | }); 213 | ($e:expr; ws*, $($tail:tt)*) => ({ 214 | let _line: &str = $e; 215 | lex!(_line.trim_left(); $($tail)*) 216 | }); 217 | ($e:expr; str, $($tail:tt)*) => ({ 218 | !$e.is_empty() && lex!(""; $($tail)*) // optimization! 219 | }); 220 | ($e:expr; str*, $($tail:tt)*) => ({ 221 | lex!(""; $($tail)*) // optimization! 222 | }); 223 | ($e:expr; $t:ty, $($tail:tt)*) => ({ 224 | let mut _dummy: $t; // unused 225 | lex!($e; $t -> _dummy, $($tail)*) 226 | }); 227 | ($e:expr; lit $lit:expr, $($tail:tt)*) => ({ 228 | ::util::lex::prefix_shifted($e, $lit).map_or(false, |_line| { 229 | lex!(_line; $($tail)*) 230 | }) 231 | }); 232 | 233 | ($e:expr; str -> $dst:expr) => (lex!($e; str -> $dst, )); 234 | ($e:expr; str -> $dst:expr, ws*) => (lex!($e; str -> $dst, ws*, )); 235 | ($e:expr; str* -> $dst:expr) => (lex!($e; str* -> $dst, )); 236 | ($e:expr; str* -> $dst:expr, ws*) => (lex!($e; str* -> $dst, ws*, )); 237 | ($e:expr; $t:ty -> $dst:expr) => (lex!($e; $t -> $dst, )); 238 | 239 | ($e:expr; ws) => (lex!($e; ws, )); 240 | ($e:expr; ws*) => (lex!($e; ws*, )); 241 | ($e:expr; str) => (lex!($e; str, )); 242 | ($e:expr; str*) => (lex!($e; str*, )); 243 | ($e:expr; $t:ty) => (lex!($e; $t, )); 244 | ($e:expr; lit $lit:expr) => (lex!($e; lit $lit, )) 245 | ) 246 | -------------------------------------------------------------------------------- /src/engine/resource.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md and LICENSE.txt for details. 4 | 5 | //! Resource management. 6 | 7 | use std::str; 8 | 9 | use sdl::video::{Surface, RGB, SurfaceFlag}; 10 | use sdl_image; 11 | use sdl_mixer::Chunk; 12 | use ext::smpeg::MPEG; 13 | use util::filesearch::SearchContext; 14 | use gfx::gl::PreparedSurface; 15 | 16 | /// The width of BGA, or the width of screen for the exclusive mode. 17 | pub const BGAW: uint = 256; 18 | /// The height of BGA, or the height of screen for the exclusive mode. 19 | pub const BGAH: uint = 256; 20 | 21 | /// An internal sampling rate for SDL_mixer. Every chunk loaded is first converted to 22 | /// this sampling rate for the purpose of mixing. 23 | pub const SAMPLERATE: i32 = 44100; 24 | /// The number of bytes in the chunk converted to an internal sampling rate. 25 | pub const BYTESPERSEC: i32 = SAMPLERATE * 2 * 2; // stereo, 16 bits/sample 26 | 27 | /// Alternative file extensions for sound resources. 28 | static SOUND_EXTS: &'static [&'static str] = &[".WAV", ".OGG", ".MP3"]; 29 | /// Alternative file extensions for image resources. 30 | static IMAGE_EXTS: &'static [&'static str] = &[".BMP", ".PNG", ".JPG", ".JPEG", ".GIF"]; 31 | 32 | /// A wrapper for `SearchContext::resolve_relative_path` which returns `Result`. 33 | fn resolve_relative_path_result(search: &mut SearchContext, basedir: &Path, path: &str, 34 | exts: &[&str]) -> Result { 35 | match search.resolve_relative_path(basedir, path, exts) { 36 | Some(path) => Ok(path), 37 | None => Err(format!("file not found")), 38 | } 39 | } 40 | 41 | pub trait SearchContextAdditions { 42 | fn resolve_relative_path_for_sound(&mut self, path: &str, 43 | basedir: &Path) -> Result; 44 | fn resolve_relative_path_for_image(&mut self, path: &str, 45 | basedir: &Path) -> Result; 46 | } 47 | 48 | impl SearchContextAdditions for SearchContext { 49 | /// Resolves the relative path for the use by `LoadedSoundlike::new`. 50 | fn resolve_relative_path_for_sound(&mut self, path: &str, 51 | basedir: &Path) -> Result { 52 | resolve_relative_path_result(self, basedir, path, SOUND_EXTS) 53 | } 54 | 55 | /// Resolves the relative path for the use by `LoadedImagelike::new`. 56 | fn resolve_relative_path_for_image(&mut self, path: &str, 57 | basedir: &Path) -> Result { 58 | use std::ascii::AsciiExt; 59 | // preserve extensions for the movie files 60 | if path.to_ascii_lower()[].ends_with(".mpg") { 61 | resolve_relative_path_result(self, basedir, path, [][]) 62 | } else { 63 | resolve_relative_path_result(self, basedir, path, IMAGE_EXTS) 64 | } 65 | } 66 | } 67 | 68 | /// Sound resource associated to `SoundRef`. It contains the actual SDL_mixer chunk that can be 69 | /// readily played. 70 | pub enum Soundlike { 71 | /// No sound resource is associated, or error occurred while loading. 72 | None, 73 | /// Sound resource is associated. 74 | Sound(Chunk) 75 | } 76 | 77 | impl Soundlike { 78 | /// Returns the associated chunk if any. 79 | pub fn chunk<'r>(&'r self) -> Option<&'r Chunk> { 80 | match *self { 81 | Soundlike::None => None, 82 | Soundlike::Sound(ref chunk) => Some(chunk) 83 | } 84 | } 85 | 86 | /// Returns the associated chunk if any. 87 | pub fn chunk_mut<'r>(&'r mut self) -> Option<&'r mut Chunk> { 88 | match *self { 89 | Soundlike::None => None, 90 | Soundlike::Sound(ref mut chunk) => Some(chunk) 91 | } 92 | } 93 | 94 | /// Returns the length of associated sound chunk in seconds. This is used for determining 95 | /// the actual duration of the song in presence of key and background sounds, so it may 96 | /// return 0.0 if no sound is present. 97 | pub fn duration(&self) -> f64 { 98 | match *self { 99 | Soundlike::None => 0.0, 100 | Soundlike::Sound(ref chunk) => { 101 | let chunk = chunk.to_ll_chunk(); 102 | (unsafe {(*chunk).alen} as f64) / (BYTESPERSEC as f64) 103 | } 104 | } 105 | } 106 | } 107 | 108 | /// Same as `Soundlike` but no managed pointer. This version of sound resource can be 109 | /// transferred across tasks and thus used for the worker model. 110 | // 111 | // Rust: the very existence of this enum and `LoadedImagelike` is due to the problem in 112 | // cross-task owned pointer containing a managed pointer. (#8983) 113 | pub enum LoadedSoundlike { 114 | None, 115 | Sound(Chunk) 116 | } 117 | 118 | impl LoadedSoundlike { 119 | /// Loads a sound resource. 120 | pub fn new(path: &Path) -> Result { 121 | let res = try!(Chunk::from_wav(path)); 122 | Ok(LoadedSoundlike::Sound(res)) 123 | } 124 | 125 | /// Creates a `Soundlike` instance. There is no turning back. 126 | pub fn wrap(self) -> Soundlike { 127 | match self { 128 | LoadedSoundlike::None => Soundlike::None, 129 | LoadedSoundlike::Sound(chunk) => Soundlike::Sound(chunk) 130 | } 131 | } 132 | } 133 | 134 | /// Image resource associated to `ImageRef`. It can be either a static image or a movie, and 135 | /// both contains an SDL surface that can be blitted to the screen. 136 | pub enum Imagelike { 137 | /// No image resource is associated, or error occurred while loading. 138 | None, 139 | /// A static image is associated. The surface may have a transparency which is already 140 | /// handled by `LoadedImagelike::new`. 141 | Image(PreparedSurface), 142 | /// A movie is associated. A playback starts when `start_movie` method is called, and stops 143 | /// when `stop_animating` is called. An associated surface is updated from the separate thread 144 | /// during the playback. 145 | Movie(PreparedSurface, MPEG) 146 | } 147 | 148 | impl Imagelike { 149 | /// Returns an associated surface if any. 150 | pub fn surface<'r>(&'r self) -> Option<&'r PreparedSurface> { 151 | match *self { 152 | Imagelike::None => None, 153 | Imagelike::Image(ref surface) | Imagelike::Movie(ref surface,_) => Some(surface) 154 | } 155 | } 156 | 157 | /// Stops the animation/movie playback if possible. 158 | pub fn stop_animating(&self) { 159 | match *self { 160 | Imagelike::None | Imagelike::Image(_) => {} 161 | Imagelike::Movie(_,ref mpeg) => { mpeg.stop(); } 162 | } 163 | } 164 | 165 | /// Starts (or restarts, if the movie was already being played) the animation/movie playback 166 | /// if possible. 167 | pub fn start_animating(&self) { 168 | match *self { 169 | Imagelike::None | Imagelike::Image(_) => {} 170 | Imagelike::Movie(_,ref mpeg) => { mpeg.rewind(); mpeg.play(); } 171 | } 172 | } 173 | } 174 | 175 | /// Same as `Imagelike` but no managed pointer. This version of image resource can be 176 | /// transferred across tasks and thus used for the worker model. 177 | pub enum LoadedImagelike { 178 | None, 179 | Image(PreparedSurface), 180 | Movie(PreparedSurface, MPEG) 181 | } 182 | 183 | impl LoadedImagelike { 184 | /// Loads an image resource. 185 | pub fn new(path: &Path, load_movie: bool) -> Result { 186 | use std::ascii::AsciiExt; 187 | 188 | /// Converts a surface to the native display format, while preserving a transparency or 189 | /// setting a color key if required. 190 | fn to_display_format(surface: Surface) -> Result { 191 | let surface = if unsafe {(*(*surface.raw).format).Amask} != 0 { 192 | let surface = try!(surface.display_format_alpha()); 193 | surface.set_alpha([SurfaceFlag::SrcAlpha, SurfaceFlag::RLEAccel][], 255); 194 | surface 195 | } else { 196 | let surface = try!(surface.display_format()); 197 | surface.set_color_key([SurfaceFlag::SrcColorKey, SurfaceFlag::RLEAccel][], 198 | RGB(0, 0, 0)); 199 | surface 200 | }; 201 | match PreparedSurface::from_owned_surface(surface) { 202 | Ok(prepared) => Ok(prepared), 203 | Err((_surface,err)) => Err(err) 204 | } 205 | } 206 | 207 | let ext = path.extension().and_then(str::from_utf8); 208 | if ext.unwrap_or_default().eq_ignore_ascii_case("mpg") { 209 | if load_movie { 210 | let movie = try!(MPEG::from_path(path)); 211 | let surface = try!(PreparedSurface::new(BGAW, BGAH, false)); 212 | movie.enable_video(true); 213 | movie.set_loop(true); 214 | movie.set_display(surface.as_surface()); 215 | Ok(LoadedImagelike::Movie(surface, movie)) 216 | } else { 217 | Ok(LoadedImagelike::None) 218 | } 219 | } else { 220 | let surface = try!(sdl_image::load(path)); 221 | let prepared = try!(to_display_format(surface)); 222 | 223 | // PreparedSurface may destroy SDL_SRCALPHA, which is still required for alpha blitting. 224 | // for RGB images, it is effectively no-op as per-surface alpha is fully opaque. 225 | prepared.as_surface().set_alpha([SurfaceFlag::SrcAlpha, SurfaceFlag::RLEAccel][], 255); 226 | Ok(LoadedImagelike::Image(prepared)) 227 | } 228 | } 229 | 230 | /// Creates an `Imagelike` instance. Again, there is no turning back. 231 | pub fn wrap(self) -> Imagelike { 232 | match self { 233 | LoadedImagelike::None => Imagelike::None, 234 | LoadedImagelike::Image(surface) => Imagelike::Image(surface), 235 | LoadedImagelike::Movie(surface, mpeg) => Imagelike::Movie(surface, mpeg) 236 | } 237 | } 238 | } 239 | 240 | -------------------------------------------------------------------------------- /src/gfx/surface.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md for details. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or 6 | // the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! Utilities for SDL surfaces. 10 | 11 | use gfx::color::{Color, Gradient, RGB, RGBA, Blend}; 12 | 13 | pub use sdl::Rect; 14 | pub use sdl::video::Surface; 15 | use sdl::video::ll::SDL_PixelFormat; 16 | 17 | /// A trait that can be translated to point coordinates (`x` and `y` fields in `sdl::Rect`, 18 | /// hence the name). Also contains `()`. 19 | pub trait XyOpt { 20 | /// Returns point coordinates if any. 21 | fn xy_opt(&self) -> Option<(i16,i16)>; 22 | } 23 | 24 | /// Same as `XyOpt` but does not contain `()`. 25 | pub trait Xy: XyOpt { 26 | /// Returns point coordinates. 27 | fn xy(&self) -> (i16,i16); 28 | } 29 | 30 | /// A trait that can be translated to a rectangular area (`w` and `h` fields in `sdl::Rect`, 31 | /// hence the name). Also contains `()`. 32 | pub trait WhOpt { 33 | /// Returns a rectangular area if any. 34 | fn wh_opt(&self) -> Option<(u16,u16)>; 35 | } 36 | 37 | /// Same as `WhOpt` but does not contain `()`. 38 | pub trait Wh { 39 | /// Returns a rectangular area. 40 | fn wh(&self) -> (u16,u16); 41 | } 42 | 43 | impl XyOpt for () { 44 | #[inline(always)] 45 | fn xy_opt(&self) -> Option<(i16,i16)> { None } 46 | } 47 | 48 | // Rust: we can't define these with `impl XyOpt for T` due to the ambiguity. 49 | impl XyOpt for Rect { 50 | #[inline(always)] 51 | fn xy_opt(&self) -> Option<(i16,i16)> { Some((self.x, self.y)) } 52 | } 53 | 54 | impl<'r,T:XyOpt> XyOpt for &'r T { 55 | #[inline(always)] 56 | fn xy_opt(&self) -> Option<(i16,i16)> { (*self).xy_opt() } 57 | } 58 | 59 | impl Xy for Rect { 60 | #[inline(always)] 61 | fn xy(&self) -> (i16,i16) { (self.x, self.y) } 62 | } 63 | 64 | impl<'r,T:Xy> Xy for &'r T { 65 | #[inline(always)] 66 | fn xy(&self) -> (i16,i16) { (*self).xy() } 67 | } 68 | 69 | impl WhOpt for () { 70 | #[inline(always)] 71 | fn wh_opt(&self) -> Option<(u16,u16)> { None } 72 | } 73 | 74 | impl WhOpt for Rect { 75 | #[inline(always)] 76 | fn wh_opt(&self) -> Option<(u16,u16)> { Some((self.w, self.h)) } 77 | } 78 | 79 | impl WhOpt for Surface { 80 | #[inline(always)] 81 | fn wh_opt(&self) -> Option<(u16,u16)> { Some(self.get_size()) } 82 | } 83 | 84 | impl<'r,T:WhOpt> WhOpt for &'r T { 85 | #[inline(always)] 86 | fn wh_opt(&self) -> Option<(u16,u16)> { (*self).wh_opt() } 87 | } 88 | 89 | impl Wh for Rect { 90 | #[inline(always)] 91 | fn wh(&self) -> (u16,u16) { (self.w, self.h) } 92 | } 93 | 94 | impl Wh for Surface { 95 | #[inline(always)] 96 | fn wh(&self) -> (u16,u16) { self.get_size() } 97 | } 98 | 99 | impl<'r,T:Wh> Wh for &'r T { 100 | #[inline(always)] 101 | fn wh(&self) -> (u16,u16) { (*self).wh() } 102 | } 103 | 104 | /// A helper trait for defining every implementations for types `(T1,T2)` where `T1` and `T2` is 105 | /// convertible to an integer. 106 | trait ToInt16 { 107 | /// Converts to `i16`. 108 | fn to_i16(&self) -> i16; 109 | /// Converts to `u16`. 110 | fn to_u16(&self) -> u16; 111 | } 112 | 113 | macro_rules! define_ToInt16( 114 | ($t:ty) => (impl ToInt16 for $t { 115 | #[inline(always)] 116 | fn to_i16(&self) -> i16 { *self as i16 } 117 | #[inline(always)] 118 | fn to_u16(&self) -> u16 { *self as u16 } 119 | }) 120 | ) 121 | 122 | define_ToInt16!(int) 123 | define_ToInt16!(uint) 124 | define_ToInt16!(i8) 125 | define_ToInt16!(i16) 126 | define_ToInt16!(i32) 127 | define_ToInt16!(i64) 128 | define_ToInt16!(u8) 129 | define_ToInt16!(u16) 130 | define_ToInt16!(u32) 131 | define_ToInt16!(u64) 132 | 133 | impl XyOpt for (X,Y) { 134 | #[inline(always)] 135 | fn xy_opt(&self) -> Option<(i16,i16)> { 136 | let (x, y) = self.clone(); 137 | Some((x.to_i16(), y.to_i16())) 138 | } 139 | } 140 | 141 | impl Xy for (X,Y) { 142 | #[inline(always)] 143 | fn xy(&self) -> (i16,i16) { 144 | let (x, y) = self.clone(); 145 | (x.to_i16(), y.to_i16()) 146 | } 147 | } 148 | 149 | impl WhOpt for (W,H) { 150 | #[inline(always)] 151 | fn wh_opt(&self) -> Option<(u16,u16)> { 152 | let (w, h) = self.clone(); 153 | Some((w.to_u16(), h.to_u16())) 154 | } 155 | } 156 | 157 | impl Wh for (W,H) { 158 | #[inline(always)] 159 | fn wh(&self) -> (u16,u16) { 160 | let (w, h) = self.clone(); 161 | (w.to_u16(), h.to_u16()) 162 | } 163 | } 164 | 165 | /// Constructs an `sdl::Rect` from given point coordinates. Fills `w` and `h` fields to 0 166 | /// as expected by the second `sdl::Rect` argument from `SDL_BlitSurface`. 167 | #[inline(always)] 168 | pub fn rect_from_xy(xy: XY) -> Rect { 169 | let (x, y) = xy.xy(); 170 | Rect { x: x, y: y, w: 0, h: 0 } 171 | } 172 | 173 | /// Constructs an `sdl::Rect` from given point coordinates and optional rectangular area. 174 | /// `rect_from_xywh(xy, ())` equals to `rect_from_xy(xy)`. 175 | #[inline(always)] 176 | pub fn rect_from_xywh(xy: XY, wh: WH) -> Rect { 177 | let (x, y) = xy.xy(); 178 | let (w, h) = wh.wh_opt().unwrap_or((0, 0)); 179 | Rect { x: x, y: y, w: w, h: h } 180 | } 181 | 182 | /// Additions to `sdl::video::Surface`. They replace their `_rect` suffixed counterparts, 183 | /// which are generally annoying to work with. 184 | pub trait SurfaceAreaUtil { 185 | /// An alternative interface to `set_clip_rect`. 186 | fn set_clip_area(&self, xy: XY, wh: WH); 187 | /// An alternative interface to `blit_rect`. 188 | fn blit_area(&self, src: &Surface, 189 | srcxy: SrcXY, dstxy: DstXY, wh: WH) -> bool; 190 | /// An alternative interface to `fill_rect`. 191 | fn fill_area(&self, xy: XY, wh: WH, color: Color) -> bool; 192 | } 193 | 194 | impl SurfaceAreaUtil for Surface { 195 | #[inline(always)] 196 | fn set_clip_area(&self, xy: XY, wh: WH) { 197 | let rect = rect_from_xywh(xy, wh); 198 | self.set_clip_rect(&rect) 199 | } 200 | 201 | #[inline(always)] 202 | fn blit_area(&self, src: &Surface, 203 | srcxy: SrcXY, dstxy: DstXY, wh: WH) -> bool { 204 | let srcrect = rect_from_xywh(srcxy, wh); 205 | let dstrect = dstxy.xy_opt().map(|xy| rect_from_xywh(xy, &srcrect)); 206 | self.blit_rect(src, Some(srcrect), dstrect) 207 | } 208 | 209 | #[inline(always)] 210 | fn fill_area(&self, xy: XY, wh: WH, color: Color) -> bool { 211 | let rect = rect_from_xywh(xy, wh); 212 | self.fill_rect(Some(rect), color) 213 | } 214 | } 215 | 216 | /// A proxy to `sdl::video::Surface` for the direct access to pixels. For now, it is for 32 bits 217 | /// per pixel only. 218 | pub struct SurfacePixels<'r> { 219 | fmt: *mut SDL_PixelFormat, 220 | width: uint, 221 | height: uint, 222 | pitch: uint, 223 | pixels: &'r mut [u32] 224 | } 225 | 226 | /// A trait for the direct access to pixels. 227 | pub trait SurfacePixelsUtil { 228 | /// Grants the direct access to pixels. Also locks the surface as needed, so you can't blit 229 | /// during working with pixels. 230 | fn with_pixels(&self, f: |pixels: &mut SurfacePixels| -> R) -> R; 231 | } 232 | 233 | impl SurfacePixelsUtil for Surface { 234 | fn with_pixels(&self, f: |pixels: &mut SurfacePixels| -> R) -> R { 235 | self.with_lock(|pixels| { 236 | let fmt = unsafe {(*self.raw).format}; 237 | let pitch = unsafe {((*self.raw).pitch / 4) as uint}; 238 | let pixels = unsafe {::std::mem::transmute(pixels)}; 239 | let mut proxy = SurfacePixels { fmt: fmt, width: self.get_width() as uint, 240 | height: self.get_height() as uint, 241 | pitch: pitch, pixels: pixels }; 242 | f(&mut proxy) 243 | }) 244 | } 245 | } 246 | 247 | impl<'r> SurfacePixels<'r> { 248 | /// Returns a pixel at given position. (C: `getpixel`) 249 | pub fn get_pixel(&self, x: uint, y: uint) -> Color { 250 | Color::from_mapped(self.pixels[x + y * self.pitch], self.fmt as *const _) 251 | } 252 | 253 | /// Returns a pixel at given position, only when the position is valid. 254 | pub fn get_pixel_checked(&self, x: uint, y: uint) -> Option { 255 | if x < self.width && y < self.height { 256 | Some(self.get_pixel(x, y)) 257 | } else { 258 | None 259 | } 260 | } 261 | 262 | /// Sets a pixel to given position. (C: `putpixel`) 263 | pub fn put_pixel(&mut self, x: uint, y: uint, c: Color) { 264 | self.pixels[x + y * self.pitch] = c.to_mapped(self.fmt as *const _); 265 | } 266 | 267 | /// Sets a pixel to given position, only when the position is valid. 268 | /// Returns true when the pixel has really been set. 269 | pub fn put_pixel_checked(&mut self, x: uint, y: uint, c: Color) -> bool { 270 | if x < self.width && y < self.height { 271 | self.put_pixel(x, y, c); 272 | true 273 | } else { 274 | false 275 | } 276 | } 277 | 278 | /// Sets or blends (if `c` is `RGBA`) a pixel to given position. (C: `putblendedpixel`) 279 | pub fn put_blended_pixel(&mut self, x: uint, y: uint, c: Color) { 280 | match c { 281 | RGB(..) => self.put_pixel(x, y, c), 282 | RGBA(r,g,b,a) => match self.get_pixel(x, y) { 283 | RGB(r2,g2,b2) | RGBA(r2,g2,b2,_) => { 284 | let grad = Gradient { zero: RGB(r,g,b), one: RGB(r2,g2,b2) }; 285 | self.put_pixel(x, y, grad.blend(a as int, 255)); 286 | } 287 | } 288 | } 289 | } 290 | 291 | /// Sets or blends (if `c` is `RGBA`) a pixel to given position, 292 | /// only when the position is valid. 293 | /// Returns true when the pixel has really been set. 294 | pub fn put_blended_pixel_checked(&mut self, x: uint, y: uint, c: Color) -> bool { 295 | if x < self.width && y < self.height { 296 | self.put_blended_pixel(x, y, c); 297 | true 298 | } else { 299 | false 300 | } 301 | } 302 | 303 | } 304 | 305 | -------------------------------------------------------------------------------- /src/ui/viewing.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md and LICENSE.txt for details. 4 | 5 | //! Viewer portion of the game play screen. Also shared by exclusive modes. 6 | 7 | use std::rc::Rc; 8 | use std::cell::RefCell; 9 | 10 | use opengles::gl2 as gl; 11 | use ext::smpeg::SMPEG_PLAYING; 12 | use format::obj::{NLAYERS, BGALayer, BGARef}; 13 | use format::bms::ImageRef; 14 | use gfx::color::RGBA; 15 | use gfx::surface::SurfaceAreaUtil; 16 | use gfx::gl::{Texture2D, PreparedSurface, FrameBuffer}; 17 | use gfx::draw::TexturedDrawingTraits; 18 | use gfx::screen::Screen; 19 | use engine::resource::{BGAW, BGAH, Imagelike}; 20 | use engine::player::{BGAState, initial_bga_state, Player}; 21 | use ui::common::{Ticker, update_line}; 22 | use ui::scene::{Scene, SceneOptions, SceneCommand}; 23 | 24 | trait Uploadable { 25 | /// Uploads an associated surface to the texture if any. 26 | fn upload_to_texture(&self, texture: &Texture2D); 27 | /// Returns true if the resource should be updated continuously (i.e. movies or animations). 28 | fn should_always_upload(&self) -> bool; 29 | } 30 | 31 | impl Uploadable for Imagelike { 32 | fn upload_to_texture(&self, texture: &Texture2D) { 33 | match *self { 34 | Imagelike::None => {} 35 | Imagelike::Image(ref surface) | Imagelike::Movie(ref surface,_) => { 36 | texture.upload_surface(surface, false, false); 37 | } 38 | } 39 | } 40 | 41 | fn should_always_upload(&self) -> bool { 42 | match *self { 43 | Imagelike::None | Imagelike::Image(_) => false, 44 | Imagelike::Movie(_,ref mpeg) => mpeg.status() == SMPEG_PLAYING 45 | } 46 | } 47 | } 48 | 49 | /// The canvas to which the BGA is drawn. 50 | pub struct BGACanvas { 51 | /// The current BGA states. 52 | state: BGAState, 53 | /// Per-layer textures. 54 | textures: Vec, 55 | /// The internal canvas texture. This is what the caller should render. 56 | canvas: Texture2D, 57 | /// The frame buffer associated to the canvas. 58 | framebuf: FrameBuffer, 59 | /// The scratch surface for partial blitting. 60 | scratch: PreparedSurface, 61 | } 62 | 63 | /// Uploads the image pointed by the BGA reference to the texture. 64 | /// It performs a necessary clipping for `BGARef::SlicedImage`. 65 | /// `force` should be set to true when the image has to be updated immediately. 66 | fn upload_bga_ref_to_texture(bgaref: &BGARef, imgres: &[Imagelike], 67 | texture: &Texture2D, scratch: &PreparedSurface, force: bool) { 68 | match *bgaref { 69 | BGARef::Image(iref) if force || imgres[**iref as uint].should_always_upload() => { 70 | imgres[**iref as uint].upload_to_texture(texture); 71 | } 72 | BGARef::SlicedImage(iref, ref slice) 73 | if force || imgres[**iref as uint].should_always_upload() => { 74 | scratch.as_surface().fill(RGBA(0, 0, 0, 0)); 75 | for surface in imgres[**iref as uint].surface().into_iter() { 76 | // this requires SDL_SRCALPHA flags in `surface` (and not `scratch`). 77 | // see `LoadedImagelike::new` for relevant codes. 78 | scratch.as_surface().blit_area(surface.as_surface(), (slice.sx, slice.sy), 79 | (slice.dx, slice.dy), (slice.w, slice.h)); 80 | } 81 | texture.upload_surface(scratch, false, false); 82 | } 83 | _ => {} 84 | } 85 | } 86 | 87 | impl BGACanvas { 88 | /// Creates an initial canvas state and resources. 89 | pub fn new(imgres: &[Imagelike]) -> BGACanvas { 90 | let state = initial_bga_state(); 91 | 92 | let scratch = match PreparedSurface::new(BGAW, BGAH, true) { 93 | Ok(surface) => surface, 94 | Err(err) => die!("PreparedSurface::new failed: {}", err) 95 | }; 96 | 97 | let textures = state.iter().map(|iref| { 98 | let texture = match Texture2D::new(BGAW, BGAH) { 99 | Ok(texture) => texture, 100 | Err(err) => die!("Texture2D::new failed: {}", err) 101 | }; 102 | upload_bga_ref_to_texture(iref, imgres, &texture, &scratch, true); 103 | texture 104 | }).collect(); 105 | 106 | let canvas = match Texture2D::new(BGAW, BGAH) { 107 | Ok(texture) => texture, 108 | Err(err) => die!("Texture2D::new failed: {}", err) 109 | }; 110 | canvas.create_storage(gl::RGB, gl::UNSIGNED_BYTE, false, false); 111 | 112 | let framebuf = FrameBuffer::from_texture(&canvas); 113 | 114 | BGACanvas { state: state, textures: textures, canvas: canvas, framebuf: framebuf, 115 | scratch: scratch } 116 | } 117 | 118 | /// Updates the BGA state. This method prepares given image resources for the next rendering, 119 | /// notably by starting and stopping the movie playback and uploading textures as needed. 120 | pub fn update(&mut self, current: &BGAState, imgres: &[Imagelike]) { 121 | for layer in range(0, NLAYERS) { 122 | if self.state[layer] != current[layer] { 123 | // TODO this design can't handle the case that a BGA layer is updated to the same 124 | // image reference, which should rewind the movie playback. 125 | if self.state[layer].as_image_ref() != current[layer].as_image_ref() { 126 | for &iref in self.state[layer].as_image_ref().into_iter() { 127 | imgres[**iref as uint].stop_animating(); 128 | } 129 | for &iref in current[layer].as_image_ref().into_iter() { 130 | imgres[**iref as uint].start_animating(); 131 | } 132 | } 133 | upload_bga_ref_to_texture(¤t[layer], imgres, 134 | &self.textures[layer], &self.scratch, true); 135 | } else { 136 | upload_bga_ref_to_texture(&self.state[layer], imgres, 137 | &self.textures[layer], &self.scratch, false); 138 | } 139 | self.state[layer] = current[layer].clone(); 140 | } 141 | } 142 | 143 | /// Renders the image resources to the internal canvas texture. 144 | pub fn render_to_texture(&self, screen: &mut Screen, layers: &[BGALayer]) { 145 | screen.render_to_framebuffer(&self.framebuf, |buf| { 146 | buf.clear(); 147 | for &layer in layers.iter() { 148 | match self.state[layer as uint] { 149 | BGARef::Blank => {} 150 | _ => { 151 | buf.draw_textured(&self.textures[layer as uint], |d| { 152 | d.rect(0.0, 0.0, BGAW as f32, BGAH as f32); 153 | }); 154 | } 155 | } 156 | } 157 | }); 158 | } 159 | 160 | /// Returns the internal canvas texture. 161 | pub fn as_texture<'r>(&'r self) -> &'r Texture2D { &self.canvas } 162 | } 163 | 164 | /// Text-only viewing scene context. Used for the exclusive mode with BGA disabled. 165 | pub struct TextualViewingScene { 166 | /// Current game play states. 167 | pub player: Player, 168 | /// Ticker used for printing to the console. 169 | pub ticker: Ticker, 170 | } 171 | 172 | impl TextualViewingScene { 173 | /// Creates a new text-only viewing scene. 174 | pub fn new(player: Player) -> Box { 175 | box TextualViewingScene { player: player, ticker: Ticker::new() } 176 | } 177 | } 178 | 179 | impl Scene for TextualViewingScene { 180 | fn activate(&mut self) -> SceneCommand { SceneCommand::Continue } 181 | 182 | fn scene_options(&self) -> SceneOptions { SceneOptions::new().fpslimit(20) } 183 | 184 | fn tick(&mut self) -> SceneCommand { 185 | if self.player.tick() {SceneCommand::Continue} else {SceneCommand::Pop} 186 | } 187 | 188 | fn render(&self) { 189 | if !self.player.opts.showinfo { return; } 190 | 191 | let elapsed = (self.player.now - self.player.origintime) / 100; 192 | let duration = (self.player.duration * 10.0) as uint; 193 | update_line(format!("{:02}:{:02}.{} / {:02}:{:02}.{} (@{pos:9.4}) | \ 194 | BPM {bpm:6.2} | {lastcombo} / {nnotes} notes", 195 | elapsed/600, elapsed/10%60, elapsed%10, 196 | duration/600, duration/10%60, duration%10, 197 | pos = self.player.cur.loc.vpos, bpm = *self.player.bpm, 198 | lastcombo = self.player.lastcombo, 199 | nnotes = self.player.infos.nnotes)[]); 200 | } 201 | 202 | fn deactivate(&mut self) { 203 | update_line(""); 204 | } 205 | 206 | fn consume(self: Box) -> Box { panic!("unreachable"); } 207 | } 208 | 209 | /// BGA-only viewing scene context. Used for the exclusive mode with BGA enabled. 210 | pub struct ViewingScene { 211 | /// The underlying text-only viewing scene context (as the BGA-only viewing scene lacks 212 | /// the on-screen display). 213 | pub parent: Box, 214 | /// Display screen. 215 | pub screen: Rc>, 216 | /// Image resources. 217 | pub imgres: Vec, 218 | /// BGA canvas. 219 | pub bgacanvas: BGACanvas, 220 | } 221 | 222 | impl ViewingScene { 223 | /// Creates a new BGA-only scene context from the pre-created screen (usually by `init_video`) 224 | /// and pre-loaded image resources. 225 | pub fn new(screen: Rc>, imgres: Vec, 226 | player: Player) -> Box { 227 | let bgacanvas = BGACanvas::new(imgres[]); 228 | box ViewingScene { parent: TextualViewingScene::new(player), 229 | screen: screen, imgres: imgres, bgacanvas: bgacanvas } 230 | } 231 | } 232 | 233 | impl Scene for ViewingScene { 234 | fn activate(&mut self) -> SceneCommand { self.parent.activate() } 235 | 236 | fn scene_options(&self) -> SceneOptions { SceneOptions::new() } 237 | 238 | fn tick(&mut self) -> SceneCommand { 239 | let cmd = self.parent.tick(); 240 | self.bgacanvas.update(&self.parent.player.bga, self.imgres[]); 241 | cmd 242 | } 243 | 244 | fn render(&self) { 245 | let mut screen = self.screen.borrow_mut(); 246 | 247 | screen.clear(); 248 | 249 | let layers = &[BGALayer::Layer1, BGALayer::Layer2, BGALayer::Layer3]; 250 | self.bgacanvas.render_to_texture(screen.deref_mut(), layers); 251 | screen.draw_textured(self.bgacanvas.as_texture(), |d| { 252 | d.rect(0.0, 0.0, BGAW as f32, BGAH as f32); 253 | }); 254 | screen.swap_buffers(); 255 | 256 | self.parent.render(); 257 | } 258 | 259 | fn deactivate(&mut self) { self.parent.deactivate() } 260 | 261 | fn consume(self: Box) -> Box { panic!("unreachable"); } 262 | } 263 | 264 | -------------------------------------------------------------------------------- /src/engine/input.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md and LICENSE.txt for details. 4 | 5 | //! Mapping from actual inputs to virtual inputs. 6 | 7 | use std::hash; 8 | use std::collections::HashMap; 9 | use sdl::event; 10 | use sdl::event::Event; 11 | 12 | use format::obj::Lane; 13 | use format::bms::Key; 14 | use engine::keyspec::{KeySpec, KeyKind}; 15 | 16 | /// Actual input. Mapped to zero or more virtual inputs by input mapping. 17 | #[deriving(PartialEq,Eq)] 18 | pub enum Input { 19 | /// Keyboard input. 20 | Key(event::Key), 21 | /// Joystick axis input. 22 | JoyAxis(uint), 23 | /// Joystick button input. 24 | JoyButton(uint), 25 | /// A special input generated by pressing the quit button or escape key. 26 | Quit, 27 | } 28 | 29 | impl hash::Hash for Input { 30 | fn hash(&self, state: &mut hash::sip::SipState) { 31 | match *self { 32 | Input::Key(key) => { 0u8.hash(state); (key as uint).hash(state); } 33 | Input::JoyAxis(axis) => { 1u8.hash(state); axis.hash(state); } 34 | Input::JoyButton(button) => { 2u8.hash(state); button.hash(state); } 35 | Input::Quit => { 3u8.hash(state); } 36 | } 37 | } 38 | } 39 | 40 | impl Input { 41 | /// Translates an SDL event to the (internal) actual input type and state. 42 | pub fn from_event(event: Event) -> Option<(Input, InputState)> { 43 | match event { 44 | Event::Quit | Event::Key(event::Key::Escape,_,_,_) => 45 | Some((Input::Quit, InputState::Positive)), 46 | Event::Key(key,true,_,_) => 47 | Some((Input::Key(key), InputState::Positive)), 48 | Event::Key(key,false,_,_) => 49 | Some((Input::Key(key), InputState::Neutral)), 50 | Event::JoyButton(_which,button,true) => 51 | Some((Input::JoyButton(button as uint), InputState::Positive)), 52 | Event::JoyButton(_which,button,false) => 53 | Some((Input::JoyButton(button as uint), InputState::Neutral)), 54 | Event::JoyAxis(_which,axis,delta) if delta > 3200 => 55 | Some((Input::JoyAxis(axis as uint), InputState::Positive)), 56 | Event::JoyAxis(_which,axis,delta) if delta < -3200 => 57 | Some((Input::JoyAxis(axis as uint), InputState::Negative)), 58 | Event::JoyAxis(_which,axis,_delta) => 59 | Some((Input::JoyAxis(axis as uint), InputState::Neutral)), 60 | _ => None 61 | } 62 | } 63 | } 64 | 65 | /// Virtual input. 66 | #[deriving(PartialEq,Eq)] 67 | pub enum VirtualInput { 68 | /// Virtual input mapped to the lane. 69 | Lane(Lane), 70 | /// Speed down input (normally F3). 71 | SpeedDown, 72 | /// Speed up input (normally F4). 73 | SpeedUp, 74 | } 75 | 76 | /** 77 | * State of virtual input elements. There are three states: neutral, and positive or negative. 78 | * There is no difference between positive and negative states (the naming is arbitrary) 79 | * except for that they are distinct. 80 | * 81 | * The states should really be one of pressed (non-neutral) or unpressed (neutral) states, 82 | * but we need two non-neutral states since the actual input device with continuous values 83 | * (e.g. joystick axes) can trigger the state transition *twice* without hitting the neutral 84 | * state. We solve this problem by making the transition from negative to positive (and vice 85 | * versa) temporarily hit the neutral state. 86 | */ 87 | #[deriving(PartialEq,Eq)] 88 | pub enum InputState { 89 | /// Positive input state. Occurs when the button is pressed or the joystick axis is moved 90 | /// in the positive direction. 91 | Positive = 1, 92 | /// Neutral input state. Occurs when the button is not pressed or the joystick axis is moved 93 | /// back to the origin. 94 | Neutral = 0, 95 | /// Negative input state. Occurs when the joystick axis is moved in the negative direction. 96 | Negative = -1 97 | } 98 | 99 | impl VirtualInput { 100 | /// Returns true if the virtual input has a specified key kind in the key specification. 101 | pub fn active_in_key_spec(&self, kind: KeyKind, keyspec: &KeySpec) -> bool { 102 | match *self { 103 | VirtualInput::Lane(Lane(lane)) => keyspec.kinds[lane] == Some(kind), 104 | VirtualInput::SpeedDown | VirtualInput::SpeedUp => true 105 | } 106 | } 107 | } 108 | 109 | /// An information about an environment variable for multiple keys. 110 | struct KeySet { 111 | envvar: &'static str, 112 | envvar2: &'static str, // for compatibility with Angolmois 113 | default: &'static str, 114 | mapping: &'static [(Option, &'static [VirtualInput])], 115 | } 116 | 117 | /// A list of environment variables that set the mapping for multiple keys, and corresponding 118 | /// default values and the order of keys. 119 | static KEYSETS: &'static [KeySet] = &[ 120 | KeySet { envvar: "SNRS_1P_KEYS", 121 | envvar2: "ANGOLMOIS_1P_KEYS", 122 | default: "left shift%axis 3|z%button 3|s%button 6|x%button 2|d%button 7|\ 123 | c%button 1|f%button 4|v%axis 2|left alt", 124 | mapping: &[(Some(KeyKind::Scratch), &[VirtualInput::Lane(Lane(6))]), 125 | (Some(KeyKind::WhiteKey), &[VirtualInput::Lane(Lane(1))]), 126 | (Some(KeyKind::BlackKey), &[VirtualInput::Lane(Lane(2))]), 127 | (Some(KeyKind::WhiteKey), &[VirtualInput::Lane(Lane(3))]), 128 | (Some(KeyKind::BlackKey), &[VirtualInput::Lane(Lane(4))]), 129 | (Some(KeyKind::WhiteKey), &[VirtualInput::Lane(Lane(5))]), 130 | (Some(KeyKind::BlackKey), &[VirtualInput::Lane(Lane(8))]), 131 | (Some(KeyKind::WhiteKey), &[VirtualInput::Lane(Lane(9))]), 132 | (Some(KeyKind::FootPedal), &[VirtualInput::Lane(Lane(7))])] }, 133 | KeySet { envvar: "SNRS_2P_KEYS", 134 | envvar2: "ANGOLMOIS_2P_KEYS", 135 | default: "right alt|m|k|,|l|.|;|/|right shift", 136 | mapping: &[(Some(KeyKind::FootPedal), &[VirtualInput::Lane(Lane(36+7))]), 137 | (Some(KeyKind::WhiteKey), &[VirtualInput::Lane(Lane(36+1))]), 138 | (Some(KeyKind::BlackKey), &[VirtualInput::Lane(Lane(36+2))]), 139 | (Some(KeyKind::WhiteKey), &[VirtualInput::Lane(Lane(36+3))]), 140 | (Some(KeyKind::BlackKey), &[VirtualInput::Lane(Lane(36+4))]), 141 | (Some(KeyKind::WhiteKey), &[VirtualInput::Lane(Lane(36+5))]), 142 | (Some(KeyKind::BlackKey), &[VirtualInput::Lane(Lane(36+8))]), 143 | (Some(KeyKind::WhiteKey), &[VirtualInput::Lane(Lane(36+9))]), 144 | (Some(KeyKind::Scratch), &[VirtualInput::Lane(Lane(36+6))])] }, 145 | KeySet { envvar: "SNRS_PMS_KEYS", 146 | envvar2: "ANGOLMOIS_PMS_KEYS", 147 | default: "z|s|x|d|c|f|v|g|b", 148 | mapping: &[(Some(KeyKind::Button1), &[VirtualInput::Lane(Lane(1))]), 149 | (Some(KeyKind::Button2), &[VirtualInput::Lane(Lane(2))]), 150 | (Some(KeyKind::Button3), &[VirtualInput::Lane(Lane(3))]), 151 | (Some(KeyKind::Button4), &[VirtualInput::Lane(Lane(4))]), 152 | (Some(KeyKind::Button5), &[VirtualInput::Lane(Lane(5))]), 153 | (Some(KeyKind::Button4), &[VirtualInput::Lane(Lane(8)), 154 | VirtualInput::Lane(Lane(36+2))]), 155 | (Some(KeyKind::Button3), &[VirtualInput::Lane(Lane(9)), 156 | VirtualInput::Lane(Lane(36+3))]), 157 | (Some(KeyKind::Button2), &[VirtualInput::Lane(Lane(6)), 158 | VirtualInput::Lane(Lane(36+4))]), 159 | (Some(KeyKind::Button1), &[VirtualInput::Lane(Lane(7)), 160 | VirtualInput::Lane(Lane(36+5))])] }, 161 | KeySet { envvar: "SNRS_SPEED_KEYS", 162 | envvar2: "ANGOLMOIS_SPEED_KEYS", 163 | default: "f3|f4", 164 | mapping: &[(None, &[VirtualInput::SpeedDown]), 165 | (None, &[VirtualInput::SpeedUp])] }, 166 | ]; 167 | 168 | /// An input mapping, i.e. a mapping from the actual input to the virtual input. 169 | pub type KeyMap = HashMap; 170 | 171 | /// Reads an input mapping from the environment variables. 172 | pub fn read_keymap(keyspec: &KeySpec, getenv: |&str| -> Option) -> Result { 173 | use std::ascii::{AsciiExt, OwnedAsciiExt}; 174 | 175 | /// Finds an SDL virtual key with the given name. Matching is done case-insensitively. 176 | fn sdl_key_from_name(name: &str) -> Option { 177 | let name = name.to_ascii_lower(); 178 | unsafe { 179 | let firstkey = 0u16; 180 | let lastkey = ::std::mem::transmute(event::Key::Last); 181 | for keyidx in range(firstkey, lastkey) { 182 | let key = ::std::mem::transmute(keyidx); 183 | let keyname = event::get_key_name(key).into_ascii_lower(); 184 | if keyname == name { return Some(key); } 185 | } 186 | } 187 | None 188 | } 189 | 190 | /// Parses an `Input` value from the string. E.g. `"backspace"`, `"button 2"` or `"axis 0"`. 191 | fn parse_input(s: &str) -> Option { 192 | let mut idx = 0; 193 | let s = s.trim(); 194 | if lex!(s; lit "button", ws, uint -> idx) { 195 | Some(Input::JoyButton(idx)) 196 | } else if lex!(s; lit "axis", ws, uint -> idx) { 197 | Some(Input::JoyAxis(idx)) 198 | } else { 199 | sdl_key_from_name(s).map(|key| Input::Key(key)) 200 | } 201 | } 202 | 203 | let mut map = HashMap::new(); 204 | let add_mapping = |map: &mut KeyMap, kind: Option, 205 | input: Input, vinput: VirtualInput| { 206 | if kind.map_or(true, |kind| vinput.active_in_key_spec(kind, keyspec)) { 207 | map.insert(input, vinput); 208 | } 209 | }; 210 | 211 | for &keyset in KEYSETS.iter() { 212 | let spec = getenv(keyset.envvar).or(getenv(keyset.envvar2)); 213 | let spec = spec.unwrap_or(keyset.default.to_string()); 214 | 215 | let mut i = 0; 216 | for part in spec[].split('|') { 217 | let (kind, vinputs) = keyset.mapping[i]; 218 | for s in part.split('%') { 219 | match parse_input(s[]) { 220 | Some(input) => { 221 | for &vinput in vinputs.iter() { 222 | add_mapping(&mut map, kind, input, vinput); 223 | } 224 | } 225 | None => { 226 | return Err(format!("Unknown key name in the environment variable {}: {}", 227 | keyset.envvar, s)); 228 | } 229 | } 230 | } 231 | 232 | i += 1; 233 | if i >= keyset.mapping.len() { break; } 234 | } 235 | } 236 | 237 | for &lane in keyspec.order.iter() { 238 | let key = Key(36 + *lane as int); 239 | let kind = keyspec.kinds[*lane].unwrap(); 240 | let envvar = format!("SNRS_{}{}_KEY", key, kind.to_char()); 241 | let envvar2 = format!("ANGOLMOIS_{}{}_KEY", key, kind.to_char()); 242 | for s in getenv(envvar[]).or(getenv(envvar2[])).iter() { 243 | match parse_input(s[]) { 244 | Some(input) => { 245 | add_mapping(&mut map, Some(kind), input, VirtualInput::Lane(lane)); 246 | } 247 | None => { 248 | return Err(format!("Unknown key name in the environment variable {}: {}", 249 | envvar, *s)); 250 | } 251 | } 252 | } 253 | } 254 | 255 | Ok(map) 256 | } 257 | 258 | -------------------------------------------------------------------------------- /src/gfx/skin/ast.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md for details. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or 6 | // the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | /*! 10 | * Internal representation of Sonorous scene description language. 11 | * 12 | * The current scene description language is based on JSON serialization format. 13 | * Some examples and basic elements: 14 | * 15 | * ~~~~ {.notrust} 16 | * {"nodes": [ 17 | * "comments are (currently) written in the raw string.", 18 | * "this is a temporary measure and will be replaced with the proper comments.", 19 | * 20 | * "this renders a static rectangle.", 21 | * {"$rect": null, "at": [[8,8], ["100%-8",44]], "color": "#808080"}, 22 | * 23 | * "this renders a static text.", 24 | * {"$text": "Gallery", "at": [10,10], "size": 32, "color": "black"}, 25 | * 26 | * "the renderer has a clipping region, and this delimits the changes on it.", 27 | * [ 28 | * "this changes the clipping region.", 29 | * {"$clip": [[10,50],["100%","100%"]]}, 30 | * 31 | * "this loops over the hook. the hook provider may freely call each block.", 32 | * {"$$": "images", "$then": [ 33 | * "this renders a dynamic textured rectangle.", 34 | * "the hook provider can supply different images for `image` texture hook each time.", 35 | * {"$rect": "image.thumb", "at": [[0,0], [32,32]]}, 36 | * 37 | * "this renders a dynamic text.", 38 | * "again, the hook provider can supply different strings for scalar hook each time.", 39 | * {"$text": {"$": "image.filename"}, "at": [40,2], "size": 16, "color": "white"}, 40 | * 41 | * "the block does not group the clipping region changes,", 42 | * "so this command will apply to the next iteration.", 43 | * {"$cliptop": 36} 44 | * ], "$else": [ 45 | * "this gets rendered only when the `$then` block is never called.", 46 | * {"$text": "Oops, no images.", "at": [0,0], "size": 16, "color": "gray"} 47 | * ]}, 48 | * 49 | * "the block can be used in the `$text` command, and it can also be used as ", 50 | * "the limited switching construct. no real conditional exists, though.", 51 | * {"$text": ["Total ", {"$": "nimages"}, 52 | * {"$$text": "nimages", "1": " image", "$default": " images"}], 53 | * "at": [10,46], "size": 16, "color": [255,255,255]} 54 | * ], 55 | * 56 | * "due to the current format quirks, commas are NOT allowed at the end." 57 | * ]} 58 | * ~~~~ 59 | */ 60 | 61 | use std::collections::HashMap; 62 | 63 | use gfx::color::Color; 64 | use gfx::ratio_num::RatioNum; 65 | use gfx::skin::scalar::{Scalar, ImageSource}; 66 | 67 | /// An identifier to the hook. Different kind of hooks can share the same identifier. 68 | #[deriving(PartialEq, Eq, Hash, Clone, Show)] 69 | pub struct Id(pub String); 70 | 71 | impl Id { 72 | /// Returns a slice of the identifier for a convenient matching. 73 | pub fn as_slice<'a>(&'a self) -> &'a str { 74 | let &Id(ref id) = self; 75 | id[] 76 | } 77 | } 78 | 79 | /// Numerical expression. 80 | #[deriving(Clone, Show)] 81 | pub enum Expr { 82 | Num(RatioNum), 83 | Scalar(Id), 84 | Neg(Box), 85 | Add(Box, Box), 86 | Sub(Box, Box), 87 | Mul(Box, Box), 88 | Div(Box, Box), 89 | } 90 | 91 | impl Expr { 92 | /// Returns a value of zero. 93 | pub fn zero() -> Expr { 94 | Expr::Num(RatioNum::zero()) 95 | } 96 | 97 | /// Returns a value equal to the base value. 98 | pub fn one() -> Expr { 99 | Expr::Num(RatioNum::one()) 100 | } 101 | 102 | /// Constructs an `Expr::Neg` node or any equivalent but possibly simpler expression. 103 | pub fn neg(e: Expr) -> Expr { 104 | match e { 105 | Expr::Num(v) => Expr::Num(-v), 106 | Expr::Neg(box e) => e, 107 | e => Expr::Neg(box e), 108 | } 109 | } 110 | 111 | /// Constructs an `Expr::Add` node or any equivalent but possibly simpler expression. 112 | pub fn add(lhs: Expr, rhs: Expr) -> Expr { 113 | match (lhs, rhs) { 114 | (Expr::Num(lhs), Expr::Num(rhs)) => 115 | Expr::Num(lhs + rhs), 116 | (Expr::Add(box lhs, box Expr::Num(lhsv)), Expr::Num(rhs)) | 117 | (Expr::Add(box Expr::Num(lhsv), box lhs), Expr::Num(rhs)) => 118 | Expr::Add(box lhs, box Expr::Num(lhsv + rhs)), 119 | (Expr::Sub(box lhs, box Expr::Num(lhsv)), Expr::Num(rhs)) => 120 | Expr::Add(box lhs, box Expr::Num(rhs - lhsv)), 121 | (lhs, rhs) => 122 | Expr::Add(box lhs, box rhs), 123 | } 124 | } 125 | 126 | /// Constructs an `Expr::Sub` node or any equivalent but possibly simpler expression. 127 | pub fn sub(lhs: Expr, rhs: Expr) -> Expr { 128 | match (lhs, rhs) { 129 | (Expr::Num(lhs), Expr::Num(rhs)) => 130 | Expr::Num(lhs - rhs), 131 | (Expr::Add(box lhs, box Expr::Num(lhsv)), Expr::Num(rhs)) | 132 | (Expr::Add(box Expr::Num(lhsv), box lhs), Expr::Num(rhs)) => 133 | Expr::Sub(box lhs, box Expr::Num(rhs - lhsv)), 134 | (Expr::Sub(box lhs, box Expr::Num(lhsv)), Expr::Num(rhs)) => 135 | Expr::Sub(box lhs, box Expr::Num(lhsv + rhs)), 136 | (lhs, rhs) => 137 | Expr::Sub(box lhs, box rhs), 138 | } 139 | } 140 | 141 | /// Constructs an `Expr::Mul` node or any equivalent but possibly simpler expression. 142 | pub fn mul(lhs: Expr, rhs: Expr) -> Expr { 143 | match (lhs, rhs) { 144 | (Expr::Num(lhs), Expr::Num(RatioNum { ratio: 0.0, num: rhs })) => Expr::Num(lhs * rhs), 145 | (Expr::Num(RatioNum { ratio: 0.0, num: lhs }), Expr::Num(rhs)) => Expr::Num(rhs * lhs), 146 | // other combinations of `Expr::Num`s are type errors, but we defer the error here 147 | (lhs, rhs) => Expr::Mul(box lhs, box rhs), 148 | } 149 | } 150 | 151 | /// Constructs an `Expr::Div` node or any equivalent but possibly simpler expression. 152 | pub fn div(lhs: Expr, rhs: Expr) -> Expr { 153 | match (lhs, rhs) { 154 | (Expr::Num(lhs), Expr::Num(RatioNum { ratio: 0.0, num: rhs })) => Expr::Num(lhs / rhs), 155 | // other combinations of `Expr::Num`s are type errors, but we defer the error here 156 | (lhs, rhs) => Expr::Div(box lhs, box rhs), 157 | } 158 | } 159 | } 160 | 161 | /// Two-dimensional position. 162 | #[deriving(Clone, Show)] 163 | pub struct Pos { pub x: Expr, pub y: Expr } 164 | 165 | /// Two-dimensional rectangle. 166 | #[deriving(Clone, Show)] 167 | pub struct Rect { pub p: Pos, pub q: Pos } 168 | 169 | impl Rect { 170 | /// Returns a new, full-screen clipping rectangle. 171 | pub fn new() -> Rect { 172 | Rect { p: Pos { x: Expr::Num(RatioNum::zero()), y: Expr::Num(RatioNum::zero()) }, 173 | q: Pos { x: Expr::Num(RatioNum::one()), y: Expr::Num(RatioNum::one()) } } 174 | } 175 | } 176 | 177 | /// A block call generator. 178 | #[deriving(Clone)] 179 | pub enum Gen { 180 | // {"$$": "id", ...} maps to `block_hook` 181 | Hook(Id), 182 | // {"$$text": "id", ...} maps to `scalar_hook` 183 | // - the alternative with the exact string is called once 184 | // - due to the internal structure, a string starting with $ will only match to `$default` 185 | Text(Id), 186 | // {"$$len": "id", ...} maps to `scalar_hook` 187 | // - for non-empty string the alternative with the length (as a string) is called once 188 | // - for non-existant or empty string `$else` is called 189 | TextLen(Id), 190 | } 191 | 192 | impl Gen { 193 | /// Returns a slice of the identifier (if any) for the inspection. 194 | pub fn id<'a>(&'a self) -> &'a str { 195 | match *self { 196 | Gen::Hook(ref id) | Gen::Text(ref id) | Gen::TextLen(ref id) => id.as_slice() 197 | } 198 | } 199 | } 200 | 201 | /// The universal flow structure. 202 | #[deriving(Clone)] 203 | pub enum Block { 204 | // conditional 205 | // - `then` is called multiple times with a fresh mapping 206 | // - `else_` is called once with an original mapping when `then` is never called 207 | Cond { gen: Gen, then: Option, else_: Option }, 208 | // multi 209 | // - the hook can call other alternatives multiple times 210 | // - if there is no recognized alternative `default` gets called instead 211 | // - `else_` is called once with an original mapping when no alternative is called 212 | Multi { gen: Gen, map: HashMap, default: Option, else_: Option }, 213 | } 214 | 215 | /// The formatting specification for scalar text. 216 | #[deriving(Clone)] 217 | pub enum ScalarFormat { 218 | None, 219 | // ['+'] [".."] {'#'} {'0'} '0' [".0" {'0'}] [('*' | '/'
)] 220 | // e.g. `##000.00` prints 3.147 as `003.15`, 1234.5 as `1234.50` and -987654 as `87654.00` 221 | Num { sign: bool, minwidth: u8, maxwidth: u8, precision: u8, multiplier: f64 }, 222 | // ['+'] [".."] {'#'} {'0'} '0' [":00" [":00"]] [".0" {'0'}] [('*' | '/'
)] 223 | // e.g. `0:00:00.0` prints 23456.7 as `6:30:56.7`, `##00:00.0` prints 23456.7 as `390:56.7` 224 | // note that `minwidth`/`maxwidth` here indicates those of most significant parts. 225 | Ms { sign: bool, minwidth: u8, maxwidth: u8, precision: u8, multiplier: f64 }, 226 | Hms { sign: bool, minwidth: u8, maxwidth: u8, precision: u8, multiplier: f64 }, 227 | } 228 | 229 | /// The text source for the `$text` node. 230 | #[deriving(Clone)] 231 | pub enum TextSource { 232 | Scalar(Id, ScalarFormat), 233 | Static(String), 234 | Block(Block>), 235 | Concat(Vec), 236 | } 237 | 238 | /// The color source for the `color` field in `$rect`, `$line` and a part of `$text` nodes. 239 | /// Maps to `gfx::color::Color`. 240 | pub enum ColorSource { 241 | Scalar(Id), 242 | Static(Color), 243 | Block(Block>), 244 | } 245 | 246 | impl Clone for ColorSource { 247 | fn clone(&self) -> ColorSource { 248 | match *self { 249 | ColorSource::Scalar(ref id) => ColorSource::Scalar(id.clone()), 250 | ColorSource::Static(color) => ColorSource::Static(color), 251 | ColorSource::Block(ref block) => ColorSource::Block(block.clone()), 252 | } 253 | } 254 | } 255 | 256 | /// The linear color gradient source for the `color` field in the `$text` node. 257 | /// Maps to `gfx::color::Gradient`. 258 | #[deriving(Clone)] 259 | pub enum GradientSource { 260 | FlatColor(ColorSource), 261 | Color(/* zero */ ColorSource, /* one */ ColorSource), 262 | Block(Block>), 263 | } 264 | 265 | /// The main skin commands. 266 | #[deriving(Clone)] 267 | pub enum Node { 268 | Nothing, 269 | Debug(String), 270 | ColoredLine { from: Pos, to: Pos, color: ColorSource, opacity: f32 }, 271 | // colored rect at given position 272 | ColoredRect { at: Rect, color: ColorSource, opacity: f32 }, 273 | // textured rect at given position, optionally clipped 274 | TexturedRect { tex: Id, at: Rect, colormod: ColorSource, opacity: f32, clip: Rect }, 275 | // text with fixed anchor 276 | Text { at: Pos, size: f32, anchor: (f32,f32), color: GradientSource, 277 | zerocolor: Option, text: TextSource }, // XXX no opacity yet 278 | // clipping group, resets the clipping region after the group 279 | Group(Vec), 280 | // reclipping command 281 | Clip { at: Rect }, 282 | // block 283 | Block(Block>), 284 | } 285 | 286 | /// The top-level parsed skin data. 287 | #[deriving(Clone)] 288 | pub struct Skin { 289 | /// The predefined scalar values. 290 | pub scalars: HashMap>, 291 | /// The list of commands. 292 | pub nodes: Vec, 293 | } 294 | 295 | impl Skin { 296 | /// Converts the relative paths in the scalar data into the absolute ones. 297 | pub fn make_absolute(self, base: &Path) -> Skin { 298 | let Skin { mut scalars, nodes } = self; 299 | for (_, v) in scalars.iter_mut() { 300 | match *v { 301 | Scalar::Image(ImageSource::Path(ref mut path), _clip) => { 302 | *path = base.join(&*path); 303 | } 304 | _ => {} 305 | } 306 | } 307 | Skin { scalars: scalars, nodes: nodes } 308 | } 309 | } 310 | 311 | -------------------------------------------------------------------------------- /src/engine/keyspec.rs: -------------------------------------------------------------------------------- 1 | // This is a part of Sonorous. 2 | // Copyright (c) 2005, 2007, 2009, 2012, 2013, 2014, Kang Seonghoon. 3 | // See README.md and LICENSE.txt for details. 4 | 5 | //! Key kinds and specification. 6 | 7 | use std::str; 8 | 9 | use format::obj::{Lane, NLANES, ObjQueryOps}; 10 | use format::timeline::Timeline; 11 | use format::timeline::modf::filter_lanes; 12 | use format::bms::{Bms, Key, PlayMode}; 13 | 14 | /** 15 | * Key kinds. They define an appearance of particular lane, but otherwise ignored for the game 16 | * play. Sonorous supports several key kinds in order to cover many potential uses. 17 | * 18 | * # Defaults 19 | * 20 | * For BMS/BME, channels #11/13/15/19 and #21/23/25/29 use `WhiteKey`, #12/14/18 and #22/24/28 21 | * use `BlackKey`, #16 and #26 use `Scratch`, #17 and #27 use `FootPedal`. 22 | * 23 | * For PMS, channels #11/17/25 use `Button1`, #12/16/24 use `Button2`, #13/19/23 use `Button3`, 24 | * #14/18/22 use `Button4`, #15 uses `Button5`. 25 | */ 26 | #[deriving(PartialEq,Eq)] 27 | pub enum KeyKind { 28 | /// White key, which mimics a real white key in the musical keyboard. 29 | WhiteKey, 30 | /// White key, but rendered yellow. This is used for simulating the O2Jam interface which 31 | /// has one yellow lane (mapped to spacebar) in middle of six other lanes (mapped to normal 32 | /// keys). 33 | WhiteKeyAlt, 34 | /// Black key, which mimics a real black key in the keyboard but rendered light blue as in 35 | /// Beatmania and other games. 36 | BlackKey, 37 | /// Scratch, rendered red. Scratch lane is wider than other "keys" and normally doesn't 38 | /// count as a key. 39 | Scratch, 40 | /// Foot pedal, rendered green. Otherwise has the same properties as scratch. The choice of 41 | /// color follows that of EZ2DJ, one of the first games that used this game element. 42 | FootPedal, 43 | /// White button. This and following "buttons" come from Pop'n Music, which has nine colored 44 | /// buttons. (White buttons constitute 1st and 9th of Pop'n Music buttons.) The "buttons" 45 | /// are wider than aforementioned "keys" but narrower than scratch and foot pedal. 46 | Button1, 47 | /// Yellow button (2nd and 8th of Pop'n Music buttons). 48 | Button2, 49 | /// Green button (3rd and 7th of Pop'n Music buttons). 50 | Button3, 51 | /// Navy button (4th and 6th of Pop'n Music buttons). 52 | Button4, 53 | /// Red button (5th of Pop'n Music buttons). 54 | Button5, 55 | } 56 | 57 | impl KeyKind { 58 | /// Returns a list of all supported key kinds. 59 | // 60 | // Rust: can this method be generated on the fly? 61 | pub fn all() -> &'static [KeyKind] { 62 | static ALL: [KeyKind, ..10] = [ 63 | KeyKind::WhiteKey, 64 | KeyKind::WhiteKeyAlt, 65 | KeyKind::BlackKey, 66 | KeyKind::Scratch, 67 | KeyKind::FootPedal, 68 | KeyKind::Button1, 69 | KeyKind::Button2, 70 | KeyKind::Button3, 71 | KeyKind::Button4, 72 | KeyKind::Button5, 73 | ]; 74 | ALL[] 75 | } 76 | 77 | /// Converts a mnemonic character to an appropriate key kind. Used for parsing a key 78 | /// specification (see also `KeySpec`). 79 | pub fn from_char(c: char) -> Option { 80 | match c { 81 | 'a' => Some(KeyKind::WhiteKey), 82 | 'y' => Some(KeyKind::WhiteKeyAlt), 83 | 'b' => Some(KeyKind::BlackKey), 84 | 's' => Some(KeyKind::Scratch), 85 | 'p' => Some(KeyKind::FootPedal), 86 | 'q' => Some(KeyKind::Button1), 87 | 'w' => Some(KeyKind::Button2), 88 | 'e' => Some(KeyKind::Button3), 89 | 'r' => Some(KeyKind::Button4), 90 | 't' => Some(KeyKind::Button5), 91 | _ => None 92 | } 93 | } 94 | 95 | /// Converts an appropriate key kind to a mnemonic character. Used for environment variables 96 | /// (see also `read_keymap`). 97 | pub fn to_char(self) -> char { 98 | match self { 99 | KeyKind::WhiteKey => 'a', 100 | KeyKind::WhiteKeyAlt => 'y', 101 | KeyKind::BlackKey => 'b', 102 | KeyKind::Scratch => 's', 103 | KeyKind::FootPedal => 'p', 104 | KeyKind::Button1 => 'w', 105 | KeyKind::Button2 => 'e', 106 | KeyKind::Button3 => 'r', 107 | KeyKind::Button4 => 't', 108 | KeyKind::Button5 => 's' 109 | } 110 | } 111 | 112 | /** 113 | * Returns true if a kind counts as a "key". 114 | * 115 | * This affects the number of keys displayed in the loading screen, and reflects a common 116 | * practice of counting "keys" in many games (e.g. Beatmania IIDX has 8 lanes including one 117 | * scratch but commonly said to have 7 "keys"). 118 | */ 119 | pub fn counts_as_key(self) -> bool { 120 | self != KeyKind::Scratch && self != KeyKind::FootPedal 121 | } 122 | } 123 | 124 | /// The key specification. Specifies the order and apperance of lanes. Once determined from 125 | /// the options and BMS file, the key specification is fixed and independent of other data 126 | /// (e.g. `#PLAYER` value). 127 | pub struct KeySpec { 128 | /// The number of lanes on the left side. This number is significant only when Couple Play 129 | /// is used. 130 | pub split: uint, 131 | /// The order of significant lanes. The first `nleftkeys` lanes go to the left side and 132 | /// the remaining lanes go to the right side. 133 | pub order: Vec, 134 | /// The type of lanes. 135 | pub kinds: Vec>, 136 | } 137 | 138 | impl KeySpec { 139 | /// Returns a number of lanes that count towards "keys". Notably scratches and pedals do not 140 | /// count as keys. 141 | pub fn nkeys(&self) -> uint { 142 | let mut nkeys = 0; 143 | for kind in self.kinds.iter().filter_map(|kind| *kind) { 144 | if kind.counts_as_key() { nkeys += 1; } 145 | } 146 | nkeys 147 | } 148 | 149 | /// Returns a list of lanes on the left side, from left to right. 150 | pub fn left_lanes<'r>(&'r self) -> &'r [Lane] { 151 | assert!(self.split <= self.order.len()); 152 | self.order[..self.split] 153 | } 154 | 155 | /// Returns a list of lanes on the right side if any, from left to right. 156 | pub fn right_lanes<'r>(&'r self) -> &'r [Lane] { 157 | assert!(self.split <= self.order.len()); 158 | self.order[self.split..] 159 | } 160 | 161 | /// Removes insignificant lanes. 162 | pub fn filter_timeline(&self, timeline: &mut Timeline) { 163 | filter_lanes(timeline, self.order[]); 164 | } 165 | } 166 | 167 | /// Parses the key specification from the string. 168 | pub fn parse_key_spec(s: &str) -> Option> { 169 | let mut specs = Vec::new(); 170 | let mut s = s.trim_left(); 171 | while !s.is_empty() { 172 | let mut chan = Key::dummy(); 173 | let mut kind = '\x00'; 174 | if !lex!(s; Key -> chan, char -> kind, ws*, str* -> s, !) { 175 | return None; 176 | } 177 | match (chan, KeyKind::from_char(kind)) { 178 | (Key(chan @ 36/*1*36*/...107/*3*36-1*/), Some(kind)) => { 179 | specs.push((Lane(chan as uint - 1*36), kind)); 180 | } 181 | (_, _) => { return None; } 182 | } 183 | } 184 | Some(specs) 185 | } 186 | 187 | /// A list of well-known key specifications. 188 | static PRESETS: &'static [(&'static str, &'static str, &'static str)] = &[ 189 | // 5-key BMS, SP/DP 190 | ("5", "16s 11a 12b 13a 14b 15a", ""), 191 | ("10", "16s 11a 12b 13a 14b 15a", "21a 22b 23a 24b 25a 26s"), 192 | // 5-key BMS with a foot pedal, SP/DP 193 | ("5/fp", "16s 11a 12b 13a 14b 15a 17p", ""), 194 | ("10/fp", "16s 11a 12b 13a 14b 15a 17p", "27p 21a 22b 23a 24b 25a 26s"), 195 | // 7-key BME, SP/DP 196 | ("7", "16s 11a 12b 13a 14b 15a 18b 19a", ""), 197 | ("14", "16s 11a 12b 13a 14b 15a 18b 19a", "21a 22b 23a 24b 25a 28b 29a 26s"), 198 | // 7-key BME with a foot pedal, SP/DP 199 | ("7/fp", "16s 11a 12b 13a 14b 15a 18b 19a 17p", ""), 200 | ("14/fp", "16s 11a 12b 13a 14b 15a 18b 19a 17p", "27p 21a 22b 23a 24b 25a 28b 29a 26s"), 201 | // 9-key PMS (#PLAYER 3) 202 | ("9", "11q 12w 13e 14r 15t 22r 23e 24w 25q", ""), 203 | // 9-key PMS (BME-compatible) 204 | ("9-bme", "11q 12w 13e 14r 15t 18r 19e 16w 17q", ""), 205 | ]; 206 | 207 | /** 208 | * Determines the key specification from the preset name, in the absence of explicit key 209 | * specification with `-K` option. 210 | * 211 | * Besides from presets specified in `PRESETS`, this function also allows the following 212 | * pseudo-presets inferred from the BMS file: 213 | * 214 | * - `bms`, `bme`, `bml` or no preset: Selects one of eight presets `{5,7,10,14}[/fp]`. 215 | * - `pms`: Selects one of two presets `9` and `9-bme`. 216 | */ 217 | pub fn preset_to_key_spec(bms: &Bms, preset: Option) -> Option<(String, String)> { 218 | use std::ascii::OwnedAsciiExt; 219 | use util::std::option::StrOption; 220 | 221 | let mut present = [false, ..NLANES]; 222 | for obj in bms.timeline.objs.iter() { 223 | for &Lane(lane) in obj.object_lane().iter() { 224 | present[lane] = true; 225 | } 226 | } 227 | 228 | let preset = preset.map(|s| s.into_ascii_lower()); 229 | let preset = match preset.as_ref_slice() { 230 | None | Some("bms") | Some("bme") | Some("bml") => { 231 | let isbme = present[8] || present[9] || present[36+8] || present[36+9]; 232 | let haspedal = present[7] || present[36+7]; 233 | let nkeys = match bms.meta.mode { 234 | PlayMode::Couple | PlayMode::Double => if isbme {"14"} else {"10"}, 235 | _ => if isbme {"7" } else {"5" } 236 | }; 237 | if haspedal {nkeys.to_string() + "/fp"} else {nkeys.to_string()} 238 | }, 239 | Some("pms") => { 240 | let isbme = present[6] || present[7] || present[8] || present[9]; 241 | let nkeys = if isbme {"9-bme"} else {"9"}; 242 | nkeys.to_string() 243 | }, 244 | Some(_) => preset.unwrap() 245 | }; 246 | 247 | for &(name, leftkeys, rightkeys) in PRESETS.iter() { 248 | if name == preset[] { 249 | return Some((leftkeys.to_string(), rightkeys.to_string())); 250 | } 251 | } 252 | None 253 | } 254 | 255 | /// Parses a key specification from the options. 256 | pub fn key_spec(bms: &Bms, preset: Option, 257 | leftkeys: Option, rightkeys: Option) -> Result { 258 | use std::ascii::AsciiExt; 259 | use util::std::option::StrOption; 260 | 261 | let (leftkeys, rightkeys) = 262 | if leftkeys.is_none() && rightkeys.is_none() { 263 | let ext = bms.bmspath.as_ref().and_then(|p| p.extension()) 264 | .and_then(str::from_utf8).map(|e| e.to_ascii_lower()); 265 | let preset = 266 | if preset.is_none() && ext.as_ref_slice() == Some("pms") { 267 | Some("pms".to_string()) 268 | } else { 269 | preset 270 | }; 271 | match preset_to_key_spec(bms, preset.clone()) { 272 | Some(leftright) => leftright, 273 | None => { 274 | return Err(format!("Invalid preset name: {}", 275 | preset.as_ref_slice_or(""))); 276 | } 277 | } 278 | } else { 279 | (leftkeys.as_ref_slice_or("").to_string(), 280 | rightkeys.as_ref_slice_or("").to_string()) 281 | }; 282 | 283 | let mut keyspec = KeySpec { split: 0, order: Vec::new(), 284 | kinds: Vec::from_fn(NLANES, |_| None) }; 285 | let parse_and_add = |keyspec: &mut KeySpec, keys: &str| -> Option { 286 | match parse_key_spec(keys) { 287 | None => None, 288 | Some(ref left) if left.is_empty() => None, 289 | Some(left) => { 290 | let mut err = false; 291 | for &(lane,kind) in left.iter() { 292 | if keyspec.kinds[*lane].is_some() { err = true; break; } 293 | keyspec.order.push(lane); 294 | keyspec.kinds[mut][*lane] = Some(kind); 295 | } 296 | if err {None} else {Some(left.len())} 297 | } 298 | } 299 | }; 300 | 301 | if !leftkeys.is_empty() { 302 | match parse_and_add(&mut keyspec, leftkeys[]) { 303 | None => { return Err(format!("Invalid key spec for left hand side: {}", leftkeys)); } 304 | Some(nkeys) => { keyspec.split += nkeys; } 305 | } 306 | } else { 307 | return Err(format!("No key model is specified using -k or -K")); 308 | } 309 | if !rightkeys.is_empty() { 310 | match parse_and_add(&mut keyspec, rightkeys[]) { 311 | None => { return Err(format!("Invalid key spec for right hand side: {}", rightkeys)); } 312 | Some(nkeys) => { // no split panes except for Couple Play 313 | if bms.meta.mode != PlayMode::Couple { keyspec.split += nkeys; } 314 | } 315 | } 316 | } 317 | Ok(keyspec) 318 | } 319 | 320 | --------------------------------------------------------------------------------