├── README.MOVED.txt ├── app_icon.icns ├── app_icon.ico ├── graphics ├── trackbox.tga ├── play_Keys.tga ├── play_Status.tga ├── stats_text.tga ├── title_Exit.tga ├── title_Logo.tga ├── PlayKeysBlack.psd ├── StatsGraphics.psd ├── TitleGraphics.psd ├── play_KeyRail.tga ├── play_Status2.tga ├── title_SongBox.tga ├── InterfaceButtons.tga ├── PlayNotesBlack.psd ├── PlayNotesWhite.psd ├── play_KeyShadow.tga ├── play_KeysBlack.tga ├── score_RetrySong.tga ├── title_InputBox.tga ├── title_OutputBox.tga ├── tracks_PlaySong.tga ├── title_ChooseTracks.tga ├── tracks_BackToTitle.tga ├── play_NotesBlackColor.tga ├── play_NotesBlackShadow.tga ├── play_NotesWhiteColor.tga ├── play_NotesWhiteShadow.tga └── TrackSelectionGraphics.psd ├── testing ├── pitch_bend.mid └── test_plan.txt ├── English.lproj └── InfoPlist.strings ├── src ├── libmidi │ ├── MidiTypes.h │ ├── SynthVolume.h │ ├── Note.h │ ├── MidiTrack.h │ ├── MidiComm.h │ ├── Midi.h │ ├── MidiEvent.h │ ├── MidiUtil.h │ ├── SynthVolume.cpp │ ├── MidiTrack.cpp │ ├── MidiEvent.cpp │ └── MidiUtil.cpp ├── version.h ├── os_graphics.h ├── os.h ├── resource.h ├── UserSettings.h ├── PianoGameError.cpp ├── State_Stats.h ├── CompatibleSystem.h ├── file_selector.h ├── StringTile.cpp ├── PianoGameError.h ├── MenuLayout.cpp ├── StringTile.h ├── FrameCounter.h ├── State_TrackSelection.h ├── SharedState.h ├── Tga.h ├── State_Title.h ├── Textures.h ├── Renderer.h ├── registry.h ├── MenuLayout.h ├── UserSettings.cpp ├── State_Playing.h ├── DeviceTile.h ├── TrackTile.h ├── CompatibleSystem.cpp ├── TrackProperties.h ├── resource.rc ├── TextWriter.h ├── KeyboardDisplay.h ├── DeviceTile.cpp ├── string_util.h ├── TrackTile.cpp ├── Renderer.cpp ├── State_Stats.cpp ├── registry.cpp ├── GameState.h ├── GameState.cpp ├── Tga.cpp ├── file_selector.cpp ├── TextWriter.cpp └── State_TrackSelection.cpp ├── PianoGame.sln ├── license.txt ├── Info.plist ├── nsis_installer_script.nsi ├── readme.txt └── PianoGame.vcproj /README.MOVED.txt: -------------------------------------------------------------------------------- 1 | This project has moved to www.synthesiagame.com! 2 | 3 | -------------------------------------------------------------------------------- /app_icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/app_icon.icns -------------------------------------------------------------------------------- /app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/app_icon.ico -------------------------------------------------------------------------------- /graphics/trackbox.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/trackbox.tga -------------------------------------------------------------------------------- /graphics/play_Keys.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/play_Keys.tga -------------------------------------------------------------------------------- /graphics/play_Status.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/play_Status.tga -------------------------------------------------------------------------------- /graphics/stats_text.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/stats_text.tga -------------------------------------------------------------------------------- /graphics/title_Exit.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/title_Exit.tga -------------------------------------------------------------------------------- /graphics/title_Logo.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/title_Logo.tga -------------------------------------------------------------------------------- /testing/pitch_bend.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/testing/pitch_bend.mid -------------------------------------------------------------------------------- /graphics/PlayKeysBlack.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/PlayKeysBlack.psd -------------------------------------------------------------------------------- /graphics/StatsGraphics.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/StatsGraphics.psd -------------------------------------------------------------------------------- /graphics/TitleGraphics.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/TitleGraphics.psd -------------------------------------------------------------------------------- /graphics/play_KeyRail.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/play_KeyRail.tga -------------------------------------------------------------------------------- /graphics/play_Status2.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/play_Status2.tga -------------------------------------------------------------------------------- /graphics/title_SongBox.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/title_SongBox.tga -------------------------------------------------------------------------------- /graphics/InterfaceButtons.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/InterfaceButtons.tga -------------------------------------------------------------------------------- /graphics/PlayNotesBlack.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/PlayNotesBlack.psd -------------------------------------------------------------------------------- /graphics/PlayNotesWhite.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/PlayNotesWhite.psd -------------------------------------------------------------------------------- /graphics/play_KeyShadow.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/play_KeyShadow.tga -------------------------------------------------------------------------------- /graphics/play_KeysBlack.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/play_KeysBlack.tga -------------------------------------------------------------------------------- /graphics/score_RetrySong.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/score_RetrySong.tga -------------------------------------------------------------------------------- /graphics/title_InputBox.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/title_InputBox.tga -------------------------------------------------------------------------------- /graphics/title_OutputBox.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/title_OutputBox.tga -------------------------------------------------------------------------------- /graphics/tracks_PlaySong.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/tracks_PlaySong.tga -------------------------------------------------------------------------------- /English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /graphics/title_ChooseTracks.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/title_ChooseTracks.tga -------------------------------------------------------------------------------- /graphics/tracks_BackToTitle.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/tracks_BackToTitle.tga -------------------------------------------------------------------------------- /graphics/play_NotesBlackColor.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/play_NotesBlackColor.tga -------------------------------------------------------------------------------- /graphics/play_NotesBlackShadow.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/play_NotesBlackShadow.tga -------------------------------------------------------------------------------- /graphics/play_NotesWhiteColor.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/play_NotesWhiteColor.tga -------------------------------------------------------------------------------- /graphics/play_NotesWhiteShadow.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/play_NotesWhiteShadow.tga -------------------------------------------------------------------------------- /graphics/TrackSelectionGraphics.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndpope/pianogame/HEAD/graphics/TrackSelectionGraphics.psd -------------------------------------------------------------------------------- /src/libmidi/MidiTypes.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __MIDI_TYPES_H 6 | #define __MIDI_TYPES_H 7 | 8 | typedef long long microseconds_t; 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/version.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __PIANOGAME_VERSION_H 6 | #define __PIANOGAME_VERSION_H 7 | 8 | #include 9 | 10 | // See readme.txt for a list of what has changed between versions 11 | static const std::wstring PianoGameVersionString = L"0.6.1b"; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/os_graphics.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __OS_GRAPHICS_H 6 | #define __OS_GRAPHICS_H 7 | 8 | 9 | #include "os.h" 10 | 11 | #ifdef WIN32 12 | #include 13 | #include 14 | #else 15 | #include 16 | #include 17 | #include 18 | #include 19 | #endif 20 | 21 | 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/os.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __OS_H 6 | #define __OS_H 7 | 8 | 9 | 10 | #ifdef WIN32 11 | 12 | // Don't use the Windows supplied min/max macros. We use 13 | // the std::min and std::max functions. 14 | #ifndef NOMINMAX 15 | #define NOMINMAX 16 | #endif 17 | 18 | #include 19 | 20 | #else 21 | 22 | #include 23 | 24 | #endif 25 | 26 | 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/resource.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #define IDI_MAIN_ICON 101 6 | 7 | #ifdef APSTUDIO_INVOKED 8 | #ifndef APSTUDIO_READONLY_SYMBOLS 9 | #define _APS_NO_MFC 1 10 | #define _APS_NEXT_RESOURCE_VALUE 113 11 | #define _APS_NEXT_COMMAND_VALUE 40029 12 | #define _APS_NEXT_CONTROL_VALUE 1000 13 | #define _APS_NEXT_SYMED_VALUE 101 14 | #endif 15 | #endif 16 | -------------------------------------------------------------------------------- /src/UserSettings.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __USER_SETTINGS_H 6 | #define __USER_SETTINGS_H 7 | 8 | #include 9 | 10 | namespace UserSetting 11 | { 12 | // This must be called exactly once before any of the following will work 13 | void Initialize(const std::wstring &app_name); 14 | 15 | std::wstring Get(const std::wstring &setting, const std::wstring &default_value); 16 | void Set(const std::wstring &setting, const std::wstring &value); 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /testing/test_plan.txt: -------------------------------------------------------------------------------- 1 | 2 | - Run a song with output off. 3 | - Run a song with output on. 4 | - Run a song with input set to none. 5 | - Run a song with input set to something (but still played automatically). 6 | - Run a song with input set to something, with a You Play track. 7 | - Run a song with two You Play tracks. 8 | - Page through lists of tracks in a bigger MIDI. 9 | - Run at each major resolution, playing with a single "You Play" all the way through to score. 10 | - Alt-Tab out of and into each state including file-open dialog. 11 | 12 | 13 | - Confirm pitch bend sensitivity (RPN/NRPN data) works. 14 | pitch_bend.mid (Bent notes should match piano notes.) 15 | 16 | -------------------------------------------------------------------------------- /src/PianoGameError.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "PianoGameError.h" 6 | #include "string_util.h" 7 | 8 | using namespace std; 9 | 10 | std::wstring PianoGameError::GetErrorDescription() const 11 | { 12 | switch (m_error) 13 | { 14 | case Error_StringSpecified: return m_optional_string; 15 | 16 | case Error_BadPianoType: return L"Bad piano type specified."; 17 | case Error_BadGameState: return L"Internal Error: Piano Game entered bad game state!"; 18 | 19 | default: return WSTRING(L"Unknown PianoGameError Code (" << m_error << L")."); 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/State_Stats.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __STATE_STATS_H 6 | #define __STATE_STATS_H 7 | 8 | #include "SharedState.h" 9 | #include "GameState.h" 10 | #include "MenuLayout.h" 11 | 12 | class StatsState : public GameState 13 | { 14 | public: 15 | StatsState(const SharedState &state) 16 | : m_state(state) 17 | { } 18 | 19 | protected: 20 | virtual void Init(); 21 | virtual void Update(); 22 | virtual void Draw(Renderer &renderer) const; 23 | 24 | private: 25 | ButtonState m_continue_button; 26 | ButtonState m_back_button; 27 | 28 | std::wstring m_tooltip; 29 | 30 | SharedState m_state; 31 | }; 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/CompatibleSystem.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __COMPATIBLE_SYSTEM_H 6 | #define __COMPATIBLE_SYSTEM_H 7 | 8 | #include 9 | 10 | namespace Compatible 11 | { 12 | // Some monotonically increasing value tied to the system 13 | // clock (but not necessarily based on app-start) 14 | unsigned long GetMilliseconds(); 15 | 16 | // Shows an error box with an OK button 17 | void ShowError(const std::wstring &err); 18 | 19 | int GetDisplayWidth(); 20 | int GetDisplayHeight(); 21 | 22 | void HideMouseCursor(); 23 | void ShowMouseCursor(); 24 | 25 | // Send a message to terminate the application loop gracefully 26 | void GracefulShutdown(); 27 | }; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/file_selector.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __FILE_SELECTOR_H 6 | #define __FILE_SELECTOR_H 7 | 8 | #include 9 | 10 | namespace FileSelector 11 | { 12 | // Presents a standard "File Open" dialog box. Returns empty string in [filename] 13 | // if user presses cancel. Also, remembers last filename 14 | void RequestMidiFilename(std::wstring *filename, std::wstring *file_title); 15 | 16 | // If a filename was passed in on the command line, we 17 | // can remember it for future file-open dialogs 18 | void SetLastMidiFilename(const std::wstring &filename); 19 | 20 | // Returns a filename with no path or .mid/.midi extension 21 | std::wstring TrimFilename(const std::wstring &filename); 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /PianoGame.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 10.00 3 | # Visual C++ Express 2008 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PianoGame", "PianoGame.vcproj", "{39F61D85-03F7-4874-A767-2675F4CD1545}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Win32 = Debug|Win32 9 | Release|Win32 = Release|Win32 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {39F61D85-03F7-4874-A767-2675F4CD1545}.Debug|Win32.ActiveCfg = Debug|Win32 13 | {39F61D85-03F7-4874-A767-2675F4CD1545}.Debug|Win32.Build.0 = Debug|Win32 14 | {39F61D85-03F7-4874-A767-2675F4CD1545}.Release|Win32.ActiveCfg = Release|Win32 15 | {39F61D85-03F7-4874-A767-2675F4CD1545}.Release|Win32.Build.0 = Release|Win32 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /src/StringTile.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "StringTile.h" 6 | #include "TextWriter.h" 7 | #include "Renderer.h" 8 | 9 | StringTile::StringTile(int x, int y, Tga *graphics) : m_x(x), m_y(y), m_graphics(graphics) 10 | { 11 | whole_tile = ButtonState(0, 0, StringTileWidth, StringTileHeight); 12 | } 13 | 14 | void StringTile::Update(const MouseInfo &translated_mouse) 15 | { 16 | whole_tile.Update(translated_mouse); 17 | } 18 | 19 | void StringTile::Draw(Renderer &renderer) const 20 | { 21 | renderer.SetOffset(m_x, m_y); 22 | 23 | const Color hover = Renderer::ToColor(0xFF,0xFF,0xFF); 24 | const Color no_hover = Renderer::ToColor(0xE0,0xE0,0xE0); 25 | renderer.SetColor(whole_tile.hovering ? hover : no_hover); 26 | renderer.DrawTga(m_graphics, 0, 0); 27 | 28 | TextWriter text(20, 46, renderer, false, 14); 29 | text << m_string; 30 | 31 | renderer.ResetOffset(); 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/PianoGameError.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __PIANOGAME_ERROR_H 6 | #define __PIANOGAME_ERROR_H 7 | 8 | #include 9 | #include 10 | 11 | enum PianoGameErrorCode 12 | { 13 | Error_StringSpecified, 14 | 15 | Error_BadPianoType, 16 | Error_BadGameState 17 | }; 18 | 19 | class PianoGameError : public std::exception 20 | { 21 | public: 22 | 23 | // TODO: This would be a sweet place to add stack-trace information... 24 | 25 | PianoGameError(PianoGameErrorCode error) : m_error(error), m_optional_string(L"") { } 26 | PianoGameError(const std::wstring error) : m_error(Error_StringSpecified), m_optional_string(error) { } 27 | std::wstring GetErrorDescription() const; 28 | 29 | ~PianoGameError() throw() { } 30 | 31 | const PianoGameErrorCode m_error; 32 | 33 | private: 34 | const std::wstring m_optional_string; 35 | PianoGameError operator=(const PianoGameError&); 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007 Nicholas Piegdon 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /src/MenuLayout.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "MenuLayout.h" 6 | #include "TextWriter.h" 7 | #include "Renderer.h" 8 | 9 | namespace Layout 10 | { 11 | 12 | void DrawTitle(Renderer &renderer, const std::wstring &title) 13 | { 14 | TextWriter title_writer(ScreenMarginX, ScreenMarginY - TitleFontSize - 10, renderer, false, TitleFontSize); 15 | title_writer << title; 16 | } 17 | 18 | void DrawHorizontalRule(Renderer &renderer, int state_width, int y) 19 | { 20 | renderer.SetColor(0x50, 0x50, 0x50); 21 | renderer.DrawQuad(ScreenMarginX, y - 1, state_width - 2*ScreenMarginX, 3); 22 | } 23 | 24 | void DrawButton(Renderer &renderer, const ButtonState &button, const Tga *tga) 25 | { 26 | const static Color color = Renderer::ToColor(0xE0,0xE0,0xE0); 27 | const static Color color_hover = Renderer::ToColor(0xFF,0xFF,0xFF); 28 | 29 | renderer.SetColor(button.hovering ? color_hover : color); 30 | renderer.DrawTga(tga, button.x, button.y); 31 | } 32 | 33 | 34 | } // End namespace Layout 35 | -------------------------------------------------------------------------------- /src/StringTile.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __STRING_TILE_H 6 | #define __STRING_TILE_H 7 | 8 | #include "GameState.h" 9 | #include "MenuLayout.h" 10 | #include 11 | 12 | class Renderer; 13 | class Tga; 14 | 15 | const int StringTileWidth = 510; 16 | const int StringTileHeight = 80; 17 | 18 | class StringTile 19 | { 20 | public: 21 | StringTile(int x, int y, Tga *graphics); 22 | 23 | void Update(const MouseInfo &translated_mouse); 24 | void Draw(Renderer &renderer) const; 25 | 26 | int GetX() const { return m_x; } 27 | int GetY() const { return m_y; } 28 | 29 | bool Hit() const { return whole_tile.hit; } 30 | 31 | void SetString(const std::wstring &s) { m_string = s; } 32 | void SetTitle(const std::wstring &s) { m_title = s; } 33 | 34 | const ButtonState WholeTile() const { return whole_tile; } 35 | 36 | private: 37 | int m_x; 38 | int m_y; 39 | 40 | Tga *m_graphics; 41 | 42 | std::wstring m_string; 43 | std::wstring m_title; 44 | 45 | ButtonState whole_tile; 46 | }; 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/FrameCounter.h: -------------------------------------------------------------------------------- 1 | #ifndef __FRAME_COUNTER_H 2 | #define __FRAME_COUNTER_H 3 | 4 | class FrameCounter 5 | { 6 | public: 7 | // averaged_over_milliseconds is the length of time GetFramesPerSecond 8 | // should average the frame count over in order to smooth the rate. 9 | FrameCounter(double averaged_over_milliseconds) 10 | : m_average_over_ms(averaged_over_milliseconds), m_period_ms(0), m_frames(0), m_cached_fps(0) 11 | { 12 | if (m_average_over_ms <= 50.0) m_average_over_ms = 50.0; 13 | } 14 | 15 | void Frame(double delta_ms) 16 | { 17 | if (delta_ms < 0.0) return; 18 | 19 | m_period_ms += delta_ms; 20 | m_frames++; 21 | 22 | if (m_period_ms > m_average_over_ms) 23 | { 24 | m_cached_fps = static_cast(m_frames) / m_period_ms * 1000.0; 25 | 26 | m_frames = 0; 27 | m_period_ms = 0; 28 | } 29 | } 30 | 31 | double GetFramesPerSecond() const 32 | { 33 | return m_cached_fps; 34 | } 35 | 36 | private: 37 | double m_average_over_ms; 38 | double m_period_ms; 39 | int m_frames; 40 | 41 | double m_cached_fps; 42 | }; 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /src/State_TrackSelection.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __STATE_TRACKSELECTION_H 6 | #define __STATE_TRACKSELECTION_H 7 | 8 | #include "SharedState.h" 9 | #include "GameState.h" 10 | #include "TrackTile.h" 11 | #include "libmidi/MidiTypes.h" 12 | #include 13 | 14 | class Midi; 15 | class MidiCommOut; 16 | 17 | class TrackSelectionState : public GameState 18 | { 19 | public: 20 | TrackSelectionState(const SharedState &state); 21 | 22 | protected: 23 | virtual void Init(); 24 | virtual void Update(); 25 | virtual void Draw(Renderer &renderer) const; 26 | 27 | private: 28 | void PlayTrackPreview(microseconds_t additional_time); 29 | std::vector BuildTrackProperties() const; 30 | 31 | int m_page_count; 32 | int m_current_page; 33 | int m_tiles_per_page; 34 | 35 | bool m_preview_on; 36 | bool m_first_update_after_seek; 37 | size_t m_preview_track_id; 38 | 39 | ButtonState m_continue_button; 40 | ButtonState m_back_button; 41 | 42 | std::wstring m_tooltip; 43 | 44 | std::vector m_track_tiles; 45 | 46 | SharedState m_state; 47 | }; 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /src/SharedState.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __SHARED_STATE_H 6 | #define __SHARED_STATE_H 7 | 8 | #include 9 | #include 10 | #include "TrackProperties.h" 11 | 12 | class Midi; 13 | class MidiCommOut; 14 | class MidiCommIn; 15 | 16 | struct SongStatistics 17 | { 18 | SongStatistics() : total_note_count(0), notes_user_could_have_played(0), 19 | speed_integral(0), 20 | notes_user_actually_played(0), stray_notes(0), total_notes_user_pressed(0), 21 | longest_combo(0), score(0) { } 22 | 23 | int total_note_count; 24 | 25 | int notes_user_could_have_played; 26 | long speed_integral; 27 | 28 | int notes_user_actually_played; 29 | 30 | int stray_notes; 31 | int total_notes_user_pressed; 32 | 33 | int longest_combo; 34 | double score; 35 | 36 | }; 37 | 38 | struct SharedState 39 | { 40 | SharedState() 41 | : midi(0), midi_out(0), midi_in(0), song_speed(100) 42 | { } 43 | 44 | Midi *midi; 45 | MidiCommOut *midi_out; 46 | MidiCommIn *midi_in; 47 | 48 | SongStatistics stats; 49 | 50 | int song_speed; 51 | 52 | std::vector track_properties; 53 | std::wstring song_title; 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/Tga.h: -------------------------------------------------------------------------------- 1 | #ifndef __TGA_H 2 | #define __TGA_H 3 | 4 | #include 5 | 6 | #ifdef WIN32 7 | typedef unsigned int TextureId; 8 | #else 9 | typedef unsigned long TextureId; 10 | #endif 11 | 12 | class Tga 13 | { 14 | public: 15 | static Tga *Load(const std::wstring &resource_name); 16 | static void Release(Tga *tga); 17 | 18 | TextureId GetId() const { return m_texture_id; } 19 | unsigned int GetWidth() const { return m_width; } 20 | unsigned int GetHeight() const { return m_height; } 21 | 22 | void SetSmooth(bool smooth); 23 | 24 | private: 25 | TextureId m_texture_id; 26 | unsigned int m_width; 27 | unsigned int m_height; 28 | 29 | Tga() { } 30 | ~Tga() { } 31 | 32 | Tga(const Tga& rhs); 33 | Tga &operator=(const Tga& rhs); 34 | 35 | 36 | static Tga *LoadFromData(const unsigned char *bytes); 37 | 38 | static Tga *LoadCompressed(const unsigned char *src, unsigned char *dest, unsigned int width, unsigned int height, unsigned int bpp); 39 | static Tga *LoadUncompressed(const unsigned char *src, unsigned char *dest, unsigned int size, unsigned int width, unsigned int height, unsigned int bpp); 40 | 41 | static Tga *BuildFromParameters(const unsigned char *data, unsigned int width, unsigned int height, unsigned int bpp); 42 | }; 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /src/State_Title.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __STATE_TITLE_H 6 | #define __STATE_TITLE_H 7 | 8 | #include "SharedState.h" 9 | #include "GameState.h" 10 | #include "MenuLayout.h" 11 | #include "libmidi/MidiTypes.h" 12 | #include "DeviceTile.h" 13 | #include "StringTile.h" 14 | #include 15 | 16 | class Midi; 17 | class MidiCommOut; 18 | 19 | class Tga; 20 | 21 | class TitleState : public GameState 22 | { 23 | public: 24 | // You can pass 0 in for state.midi_out to have the title 25 | // screen pick a device for you. 26 | TitleState(const SharedState &state) 27 | : m_state(state), m_output_tile(0), m_input_tile(0), 28 | m_file_tile(0), m_skip_next_mouse_up(false) 29 | { } 30 | 31 | ~TitleState(); 32 | 33 | protected: 34 | virtual void Init(); 35 | virtual void Update(); 36 | virtual void Draw(Renderer &renderer) const; 37 | 38 | private: 39 | void PlayDevicePreview(microseconds_t delta_microseconds); 40 | 41 | ButtonState m_continue_button; 42 | ButtonState m_back_button; 43 | 44 | SharedState m_state; 45 | 46 | std::string m_last_input_note_name; 47 | std::wstring m_tooltip; 48 | 49 | DeviceTile *m_output_tile; 50 | DeviceTile *m_input_tile; 51 | StringTile *m_file_tile; 52 | 53 | bool m_skip_next_mouse_up; 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeExtensions 11 | 12 | mid 13 | midi 14 | 15 | CFBundleTypeMIMETypes 16 | 17 | audio/midi 18 | 19 | CFBundleTypeName 20 | MIDI File 21 | CFBundleTypeRole 22 | Viewer 23 | LSTypeIsPackage 24 | 25 | NSPersistentStoreTypeKey 26 | Binary 27 | 28 | 29 | CFBundleExecutable 30 | ${EXECUTABLE_NAME} 31 | CFBundleIconFile 32 | app_icon.icns 33 | CFBundleIdentifier 34 | com.synthesia 35 | CFBundleInfoDictionaryVersion 36 | 6.0 37 | CFBundleName 38 | ${PRODUCT_NAME} 39 | CFBundlePackageType 40 | APPL 41 | CFBundleSignature 42 | ???? 43 | CFBundleVersion 44 | 1.0 45 | LSUIPresentationMode 46 | 4 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/Textures.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __TEXTURES_H 6 | #define __TEXTURES_H 7 | 8 | enum Texture 9 | { 10 | TitleLogo, 11 | InterfaceButtons, 12 | 13 | ButtonRetrySong, 14 | ButtonChooseTracks, 15 | ButtonExit, 16 | ButtonBackToTitle, 17 | ButtonPlaySong, 18 | 19 | InputBox, 20 | OutputBox, 21 | SongBox, 22 | 23 | TrackPanel, 24 | 25 | StatsText, 26 | 27 | PlayStatus, 28 | PlayStatus2, 29 | PlayKeys, 30 | 31 | PlayNotesBlackColor, 32 | PlayNotesBlackShadow, 33 | PlayNotesWhiteColor, 34 | PlayNotesWhiteShadow, 35 | 36 | PlayKeyRail, 37 | PlayKeyShadow, 38 | PlayKeysBlack, 39 | 40 | _TextureEnumCount 41 | }; 42 | 43 | const static wchar_t* TextureResourceNames[_TextureEnumCount] = 44 | { 45 | L"title_Logo", 46 | L"InterfaceButtons", 47 | 48 | L"score_RetrySong", 49 | L"title_ChooseTracks", 50 | L"title_Exit", 51 | L"tracks_BackToTitle", 52 | L"tracks_PlaySong", 53 | 54 | L"title_InputBox", 55 | L"title_OutputBox", 56 | L"title_SongBox", 57 | 58 | L"trackbox", 59 | 60 | L"stats_text", 61 | 62 | L"play_Status", 63 | L"play_Status2", 64 | L"play_Keys", 65 | 66 | L"play_NotesBlackColor", 67 | L"play_NotesBlackShadow", 68 | L"play_NotesWhiteColor", 69 | L"play_NotesWhiteShadow", 70 | 71 | L"play_KeyRail", 72 | L"play_KeyShadow", 73 | L"play_KeysBlack" 74 | }; 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /src/libmidi/SynthVolume.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __SYNTH_VOLUME_H 6 | #define __SYNTH_VOLUME_H 7 | 8 | #ifdef WIN32 9 | 10 | #include 11 | #include 12 | 13 | // Windows Media Player (starting in version 11) has started changing 14 | // the MIDI synth volume to 0 after playing a MIDI file and exiting. 15 | // Seeing as how that's a common file-preview path just before starting 16 | // Piano Game, we have to combat the behavior ourselves. 17 | // 18 | // Once initialized, it checks to see if the system MIDI synth volume 19 | // is "reasonable" (above 33% or so volume), and if not, it will set 20 | // it to something like 75%. On object destruction, it sets the volume 21 | // back to its previous volume. 22 | class ReasonableSynthVolume 23 | { 24 | public: 25 | ReasonableSynthVolume(); 26 | ~ReasonableSynthVolume(); 27 | 28 | private: 29 | 30 | struct SynthVolumeState 31 | { 32 | SynthVolumeState() : old_mute(1), old_volume(0), mixer(0), 33 | mute_control_id(0), volume_control_id(0) { } 34 | 35 | LONG old_mute; 36 | DWORD old_volume; 37 | 38 | HMIXER mixer; 39 | DWORD mute_control_id; 40 | DWORD volume_control_id; 41 | }; 42 | 43 | std::vector m_states; 44 | }; 45 | 46 | #else 47 | 48 | // Don't do anything on the Mac side 49 | 50 | class ReasonableSynthVolume 51 | { 52 | public: 53 | ReasonableSynthVolume() { } 54 | }; 55 | 56 | #endif 57 | 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /src/Renderer.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __RENDERER_H 6 | #define __RENDERER_H 7 | 8 | #include "os_graphics.h" 9 | 10 | #ifdef WIN32 11 | typedef HDC Context; 12 | #else 13 | typedef AGLContext Context; 14 | #endif 15 | 16 | class Tga; 17 | class Text; 18 | class TextWriter; 19 | 20 | struct Color 21 | { 22 | int r, g, b, a; 23 | }; 24 | 25 | class Renderer 26 | { 27 | public: 28 | 29 | static Color ToColor(int r, int g, int b, int a = 0xFF); 30 | 31 | Renderer(Context context); 32 | 33 | void SwapBuffers(); 34 | 35 | // 0 will disable vsync, 1 will enable. (In Windows, >1 will skip frames.) 36 | void SetVSyncInterval(int interval = 1); 37 | 38 | void SetOffset(int x, int y) { m_xoffset = x; m_yoffset = y; } 39 | void ResetOffset() { SetOffset(0,0); } 40 | 41 | void ForceTexture(unsigned int texture_id); 42 | 43 | void SetColor(Color c); 44 | void SetColor(int r, int g, int b, int a = 0xFF); 45 | void DrawQuad(int x, int y, int w, int h); 46 | 47 | void DrawTga(const Tga *tga, int x, int y) const; 48 | void DrawTga(const Tga *tga, int x, int y, int width, int height, int src_x, int src_y) const; 49 | 50 | void DrawStretchedTga(const Tga *tga, int x, int y, int w, int h) const; 51 | void DrawStretchedTga(const Tga *tga, int x, int y, int w, int h, int src_x, int src_y, int src_w, int src_h) const; 52 | 53 | private: 54 | 55 | // NOTE: These are used externally by the friend 56 | // class TextWriter (along with the context) 57 | int m_xoffset; 58 | int m_yoffset; 59 | 60 | Context m_context; 61 | 62 | friend class Text; 63 | friend class TextWriter; 64 | }; 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /src/registry.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __REGISTRY_H 6 | #define __REGISTRY_H 7 | 8 | #include 9 | #include "os.h" 10 | 11 | // Registry simplifies reading/writing the Windows registry 12 | // (It currently does not support enumerating or deleting) 13 | class Registry 14 | { 15 | public: 16 | enum RootKey { CurrentUser, LocalMachine, CU_Run, LM_Run }; 17 | 18 | // Company is optional. Read/Write will use [rootKey]/Software/[program] if 19 | // not supplied, or [rootKey]/Software/[company]/[program] if it is supplied. 20 | // 21 | // In the event of CU_Run or LM_Run, both 'program' and 'company' are ignored. 22 | Registry(const RootKey rootKey, const std::wstring program, const std::wstring company = L""); 23 | ~Registry(); 24 | 25 | // If the key was found and read successfully, function will return true, 26 | // otherwise 'out' will be filled with defaultValue, and function will 27 | // return false. (Regardless of return value, 'out' will always be usable) 28 | const bool Read(const std::wstring keyName, std::wstring *out, const std::wstring defaultValue) const; 29 | const bool Read(const std::wstring keyName, bool *out, const bool defaultValue) const; 30 | const bool Read(const std::wstring keyName, long *out, const long defaultValue) const; 31 | const bool Read(const std::wstring keyName, int *out, const int defaultValue) const; 32 | 33 | void Write(const std::wstring keyName, const std::wstring value); 34 | void Write(const std::wstring keyName, const bool value); 35 | void Write(const std::wstring keyName, const long value); 36 | void Write(const std::wstring keyName, const int value); 37 | 38 | void Delete(const std::wstring keyName); 39 | 40 | private: 41 | bool good; 42 | HKEY key; 43 | }; 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /src/MenuLayout.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __MENU_LAYOUT_H 6 | #define __MENU_LAYOUT_H 7 | 8 | #include "GameState.h" 9 | 10 | class Renderer; 11 | class Tga; 12 | 13 | struct ButtonState 14 | { 15 | ButtonState() : hovering(false), depressed(false), x(0), y(0), w(0), h(0) { } 16 | ButtonState(int x, int y, int w, int h) : hovering(false), depressed(false), x(x), y(y), w(w), h(h) { } 17 | 18 | void Update(const MouseInfo &mouse) 19 | { 20 | hovering = mouse.x > x && mouse.x < x+w && mouse.y > y && mouse.y < y+h; 21 | depressed = hovering && mouse.held.left; 22 | hit = hovering && mouse.released.left; 23 | } 24 | 25 | // Simple mouse over 26 | bool hovering; 27 | 28 | // Mouse over while (left) button is held down 29 | bool depressed; 30 | 31 | // Mouse over just as the (left) button is released 32 | bool hit; 33 | 34 | int x, y; 35 | int w, h; 36 | }; 37 | 38 | // Macro to turn replace Renderer::DrawTga()'s 4 parameters with one 39 | #define BUTTON_RECT(button) ((button).x), ((button).y), ((button).w), ((button).h) 40 | 41 | namespace Layout 42 | { 43 | void DrawTitle(Renderer &renderer, const std::wstring &title); 44 | void DrawHorizontalRule(Renderer &renderer, int state_width, int y); 45 | void DrawButton(Renderer &renderer, const ButtonState &button, const Tga *tga); 46 | 47 | // Pixel margin forced at edges of screen 48 | const static int ScreenMarginX = 16; 49 | const static int ScreenMarginY = 86; 50 | 51 | const static int TitleFontSize = 20; 52 | const static int ScoreFontSize = 26; 53 | const static int ButtonFontSize = 14; 54 | const static int SmallFontSize = 12; 55 | 56 | const static int ButtonWidth = 176; 57 | const static int ButtonHeight = 46; 58 | 59 | }; 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /src/libmidi/Note.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __MIDI_NOTE_H 6 | #define __MIDI_NOTE_H 7 | 8 | #include 9 | #include "MidiTypes.h" 10 | 11 | // Range of all 128 MIDI notes possible 12 | typedef unsigned int NoteId; 13 | 14 | // Arbitrary value outside the usual range 15 | const static NoteId InvalidNoteId = 2048; 16 | 17 | enum NoteState 18 | { 19 | AutoPlayed, 20 | UserPlayable, 21 | UserHit, 22 | UserMissed 23 | }; 24 | 25 | template 26 | struct GenericNote 27 | { 28 | bool operator()(const GenericNote &lhs, const GenericNote &rhs) const 29 | { 30 | if (lhs.start < rhs.start) return true; 31 | if (lhs.start > rhs.start) return false; 32 | 33 | if (lhs.end < rhs.end) return true; 34 | if (lhs.end > rhs.end) return false; 35 | 36 | if (lhs.note_id < rhs.note_id) return true; 37 | if (lhs.note_id > rhs.note_id) return false; 38 | 39 | if (lhs.track_id < rhs.track_id) return true; 40 | if (lhs.track_id > rhs.track_id) return false; 41 | 42 | return false; 43 | } 44 | 45 | T start; 46 | T end; 47 | NoteId note_id; 48 | size_t track_id; 49 | 50 | // We have to drag a little extra info around so we can 51 | // play the user's input correctly 52 | unsigned char channel; 53 | int velocity; 54 | 55 | NoteState state; 56 | }; 57 | 58 | // Note keeps the internal pulses found in the MIDI file which are 59 | // independent of tempo or playback speed. TranslatedNote contains 60 | // the exact (translated) microsecond that notes start and stop on 61 | // based on a given playback speed, after dereferencing tempo changes. 62 | typedef GenericNote Note; 63 | typedef GenericNote TranslatedNote; 64 | 65 | typedef std::set NoteSet; 66 | typedef std::set TranslatedNoteSet; 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /src/UserSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "string_util.h" 2 | #include "os.h" 3 | 4 | #ifdef WIN32 5 | #include "registry.h" 6 | #endif 7 | 8 | using namespace std; 9 | 10 | namespace UserSetting 11 | { 12 | 13 | #ifdef WIN32 14 | 15 | static bool g_initialized(false); 16 | static std::wstring g_app_name(L""); 17 | 18 | void Initialize(const std::wstring &app_name) 19 | { 20 | if (g_initialized) return; 21 | g_app_name = app_name; 22 | g_initialized = true; 23 | } 24 | 25 | std::wstring Get(const std::wstring &setting, const std::wstring &default_value) 26 | { 27 | if (!g_initialized) return default_value; 28 | 29 | Registry reg(Registry::CurrentUser, g_app_name); 30 | 31 | wstring result; 32 | reg.Read(setting, &result, default_value); 33 | 34 | return result; 35 | } 36 | 37 | void Set(const std::wstring &setting, const std::wstring &value) 38 | { 39 | if (!g_initialized) return; 40 | 41 | Registry reg(Registry::CurrentUser, g_app_name); 42 | reg.Write(setting, value); 43 | } 44 | 45 | #else 46 | 47 | void Initialize(const std::wstring &app_name) 48 | { 49 | // Do nothing. Mac side doesn't need this. 50 | } 51 | 52 | std::wstring Get(const std::wstring &setting, const std::wstring &default_value) 53 | { 54 | CFStringRef val = (CFStringRef)CFPreferencesCopyAppValue(MacStringFromWide(setting).get(), kCFPreferencesCurrentApplication ); 55 | if (!val) return default_value; 56 | 57 | std::wstring ret = WideFromMacString(val); 58 | CFRelease(val); 59 | 60 | return ret; 61 | } 62 | 63 | void Set(const std::wstring &setting, const std::wstring &value) 64 | { 65 | CFPreferencesSetAppValue(MacStringFromWide(setting).get(), MacStringFromWide(value).get(), kCFPreferencesCurrentApplication); 66 | CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication); 67 | } 68 | 69 | 70 | #endif 71 | 72 | }; // End namespace 73 | -------------------------------------------------------------------------------- /src/State_Playing.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __STATE_PLAYING_H 6 | #define __STATE_PLAYING_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "SharedState.h" 13 | #include "GameState.h" 14 | #include "KeyboardDisplay.h" 15 | 16 | struct TrackProperties; 17 | class Midi; 18 | class MidiCommOut; 19 | class MidiCommIn; 20 | 21 | struct ActiveNote 22 | { 23 | bool operator()(const ActiveNote &lhs, const ActiveNote &rhs) 24 | { 25 | if (lhs.note_id < rhs.note_id) return true; 26 | if (lhs.note_id > rhs.note_id) return false; 27 | 28 | if (lhs.channel < rhs.channel) return true; 29 | if (lhs.channel > rhs.channel) return false; 30 | 31 | return false; 32 | } 33 | 34 | NoteId note_id; 35 | unsigned char channel; 36 | int velocity; 37 | }; 38 | typedef std::set ActiveNoteSet; 39 | 40 | class PlayingState : public GameState 41 | { 42 | public: 43 | PlayingState(const SharedState &state); 44 | ~PlayingState(); 45 | 46 | protected: 47 | virtual void Init(); 48 | virtual void Update(); 49 | virtual void Draw(Renderer &renderer) const; 50 | 51 | private: 52 | 53 | int CalcKeyboardHeight() const; 54 | void SetupNoteState(); 55 | 56 | void ResetSong(); 57 | void Play(microseconds_t delta_microseconds); 58 | void Listen(); 59 | 60 | double CalculateScoreMultiplier() const; 61 | 62 | bool m_paused; 63 | 64 | KeyboardDisplay *m_keyboard; 65 | microseconds_t m_show_duration; 66 | TranslatedNoteSet m_notes; 67 | 68 | bool m_any_you_play_tracks; 69 | size_t m_look_ahead_you_play_note_count; 70 | 71 | ActiveNoteSet m_active_notes; 72 | 73 | bool m_first_update; 74 | 75 | SharedState m_state; 76 | int m_current_combo; 77 | 78 | double m_title_alpha; 79 | double m_max_allowed_title_alpha; 80 | 81 | // For octave sliding 82 | int m_note_offset; 83 | }; 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /src/DeviceTile.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __DEVICE_TILE_H 6 | #define __DEVICE_TILE_H 7 | 8 | #include "GameState.h" 9 | #include "MenuLayout.h" 10 | #include "TrackTile.h" 11 | #include 12 | 13 | #include "libmidi/Midi.h" 14 | #include "libmidi/MidiComm.h" 15 | 16 | class Renderer; 17 | class Tga; 18 | 19 | const int DeviceTileWidth = 510; 20 | const int DeviceTileHeight = 80; 21 | 22 | enum TrackTileGraphic; 23 | 24 | enum DeviceTileType 25 | { 26 | DeviceTileOutput, 27 | DeviceTileInput 28 | }; 29 | 30 | class DeviceTile 31 | { 32 | public: 33 | DeviceTile(int x, int y, int device_id, DeviceTileType type, const MidiCommDescriptionList &device_list, 34 | Tga *button_graphics, Tga *frame_graphics); 35 | 36 | void Update(const MouseInfo &translated_mouse); 37 | void Draw(Renderer &renderer) const; 38 | 39 | int GetX() const { return m_x; } 40 | int GetY() const { return m_y; } 41 | 42 | bool HitPreviewButton() const { return button_preview.hit; } 43 | bool IsPreviewOn() const { return m_preview_on; } 44 | void TurnOffPreview() { m_preview_on = false; } 45 | 46 | int GetDeviceId() const { return m_device_id; } 47 | 48 | const ButtonState WholeTile() const { return whole_tile; } 49 | const ButtonState ButtonPreview() const { return button_preview; } 50 | const ButtonState ButtonLeft() const { return button_mode_left; } 51 | const ButtonState ButtonRight() const { return button_mode_right; } 52 | 53 | private: 54 | DeviceTile operator=(const DeviceTile &); 55 | 56 | int m_x; 57 | int m_y; 58 | 59 | bool m_preview_on; 60 | int m_device_id; 61 | 62 | const MidiCommDescriptionList m_device_list; 63 | 64 | DeviceTileType m_tile_type; 65 | 66 | Tga *m_button_graphics; 67 | Tga *m_frame_graphics; 68 | 69 | ButtonState whole_tile; 70 | ButtonState button_preview; 71 | ButtonState button_mode_left; 72 | ButtonState button_mode_right; 73 | 74 | int LookupGraphic(TrackTileGraphic graphic, bool button_hovering) const; 75 | }; 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /src/TrackTile.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __TRACK_TILE_H 6 | #define __TRACK_TILE_H 7 | 8 | #include "GameState.h" 9 | #include "TextWriter.h" 10 | #include "TrackProperties.h" 11 | #include "MenuLayout.h" 12 | #include 13 | 14 | class Midi; 15 | class Tga; 16 | class Renderer; 17 | 18 | const int TrackTileWidth = 300; 19 | const int TrackTileHeight = 110; 20 | 21 | enum TrackTileGraphic 22 | { 23 | GraphicLeftArrow = 0, 24 | GraphicRightArrow, 25 | GraphicColor, 26 | GraphicPreviewTurnOn, 27 | GraphicPreviewTurnOff, 28 | 29 | Graphic_COUNT 30 | }; 31 | 32 | class TrackTile 33 | { 34 | public: 35 | TrackTile(int x, int y, size_t track_id, Track::TrackColor color, Track::Mode mode); 36 | 37 | void Update(const MouseInfo &translated_mouse); 38 | void Draw(Renderer &renderer, const Midi *midi, Tga *buttons, Tga *box) const; 39 | 40 | int GetX() { return m_x; } 41 | int GetY() { return m_y; } 42 | 43 | Track::Mode GetMode() const { return m_mode; } 44 | Track::TrackColor GetColor() const { return m_color; } 45 | 46 | bool HitPreviewButton() const { return button_preview.hit; } 47 | bool IsPreviewOn() const { return m_preview_on; } 48 | void TurnOffPreview() { m_preview_on = false; } 49 | 50 | size_t GetTrackId() const { return m_track_id; } 51 | 52 | const ButtonState WholeTile() const { return whole_tile; } 53 | const ButtonState ButtonPreview() const { return button_preview; } 54 | const ButtonState ButtonColor() const { return button_color; } 55 | const ButtonState ButtonLeft() const { return button_mode_left; } 56 | const ButtonState ButtonRight() const { return button_mode_right; } 57 | 58 | private: 59 | int m_x; 60 | int m_y; 61 | 62 | Track::Mode m_mode; 63 | Track::TrackColor m_color; 64 | 65 | bool m_preview_on; 66 | 67 | ButtonState whole_tile; 68 | ButtonState button_preview; 69 | ButtonState button_color; 70 | ButtonState button_mode_left; 71 | ButtonState button_mode_right; 72 | 73 | int LookupGraphic(TrackTileGraphic graphic, bool button_hovering) const; 74 | 75 | // Link to the track index of the Midi object 76 | size_t m_track_id; 77 | }; 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /src/CompatibleSystem.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "CompatibleSystem.h" 6 | #include "string_util.h" 7 | #include "version.h" 8 | #include "os.h" 9 | 10 | 11 | namespace Compatible 12 | { 13 | unsigned long GetMilliseconds() 14 | { 15 | #ifdef WIN32 16 | return timeGetTime(); 17 | #else 18 | timeval tv; 19 | gettimeofday(&tv, 0); 20 | return (tv.tv_sec * 1000) + (tv.tv_usec / 1000); 21 | #endif 22 | } 23 | 24 | 25 | void ShowError(const std::wstring &err) 26 | { 27 | const static std::wstring friendly_app_name = WSTRING(L"Piano Game " << PianoGameVersionString); 28 | const static std::wstring message_box_title = WSTRING(friendly_app_name << L" Error"); 29 | 30 | #ifdef WIN32 31 | MessageBox(0, err.c_str(), message_box_title.c_str(), MB_ICONERROR); 32 | #else 33 | 34 | DialogRef dialog; 35 | DialogItemIndex item; 36 | 37 | // The cursor might have been hidden. 38 | ShowMouseCursor(); 39 | 40 | CreateStandardAlert(kAlertStopAlert, MacStringFromWide(message_box_title).get(), MacStringFromWide(err).get(), 0, &dialog); 41 | RunStandardAlert(dialog, 0, &item); 42 | 43 | #endif 44 | } 45 | 46 | void HideMouseCursor() 47 | { 48 | #ifdef WIN32 49 | ShowCursor(false); 50 | #else 51 | CGDisplayHideCursor(kCGDirectMainDisplay); 52 | #endif 53 | } 54 | 55 | void ShowMouseCursor() 56 | { 57 | #ifdef WIN32 58 | ShowCursor(true); 59 | #else 60 | CGDisplayShowCursor(kCGDirectMainDisplay); 61 | #endif 62 | } 63 | 64 | 65 | int GetDisplayWidth() 66 | { 67 | #ifdef WIN32 68 | return GetSystemMetrics(SM_CXSCREEN); 69 | #else 70 | return int(CGDisplayBounds(kCGDirectMainDisplay).size.width); 71 | #endif 72 | } 73 | 74 | int GetDisplayHeight() 75 | { 76 | #ifdef WIN32 77 | return GetSystemMetrics(SM_CYSCREEN); 78 | #else 79 | return int(CGDisplayBounds(kCGDirectMainDisplay).size.height); 80 | #endif 81 | } 82 | 83 | 84 | void GracefulShutdown() 85 | { 86 | #ifdef WIN32 87 | PostQuitMessage(0); 88 | #else 89 | QuitApplicationEventLoop(); 90 | #endif 91 | } 92 | 93 | }; // End namespace 94 | -------------------------------------------------------------------------------- /src/TrackProperties.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __TRACK_PROPERTIES_H 6 | #define __TRACK_PROPERTIES_H 7 | 8 | #include "Renderer.h" 9 | 10 | namespace Track 11 | { 12 | 13 | enum Mode 14 | { 15 | ModePlayedAutomatically, 16 | ModeYouPlay, 17 | ModePlayedButHidden, 18 | ModeNotPlayed, 19 | 20 | ModeCount 21 | }; 22 | 23 | const static wchar_t* ModeText[ModeCount] = 24 | { 25 | L"Played Automatically", 26 | L"You Play", 27 | L"Played But Hidden", 28 | L"Not Played" 29 | }; 30 | 31 | // Based on the Open Source icon theme "Tango" color scheme 32 | // with a few changes. (e.g. Chameleon NoteBlack is a little 33 | // darker to distinguish it from NoteWhite, ScarletRed is a 34 | // little brighter to make it easier on the eyes, etc.) 35 | const static int ColorCount = 8; 36 | const static int UserSelectableColorCount = ColorCount - 2; 37 | enum TrackColor 38 | { 39 | TangoSkyBlue = 0, 40 | TangoChameleon, 41 | TangoOrange, 42 | TangoButter, 43 | TangoPlum, 44 | TangoScarletRed, 45 | 46 | FlatGray, 47 | MissedNote 48 | }; 49 | 50 | const static Color ColorNoteWhite[ColorCount] = { 51 | { 114, 159, 207, 0xFF }, 52 | { 138, 226, 52, 0xFF }, 53 | { 252, 175, 62, 0xFF }, 54 | { 252, 235, 87, 0xFF }, 55 | { 173, 104, 180, 0xFF }, 56 | { 238, 94, 94, 0xFF }, 57 | 58 | { 90, 90, 90, 0xFF }, 59 | { 60, 60, 60, 0xFF } 60 | }; 61 | 62 | const static Color ColorNoteHit[ColorCount] = { 63 | { 192, 222, 255, 0xFF }, 64 | { 203, 255, 152, 0xFF }, 65 | { 255, 216, 152, 0xFF }, 66 | { 255, 247, 178, 0xFF }, 67 | { 255, 218, 251, 0xFF }, 68 | { 255, 178, 178, 0xFF }, 69 | 70 | { 180, 180, 180, 0xFF }, 71 | { 60, 60, 60, 0xFF } 72 | }; 73 | 74 | const static Color ColorNoteBlack[ColorCount] = { 75 | { 52, 101, 164, 0xFF }, 76 | { 86, 157, 17, 0xFF }, 77 | { 245, 121, 0, 0xFF }, 78 | { 218, 195, 0, 0xFF }, 79 | { 108, 76, 113, 0xFF }, 80 | { 233, 49, 49, 0xFF }, 81 | 82 | { 90, 90, 90, 0xFF }, 83 | { 60, 60, 60, 0xFF } 84 | }; 85 | 86 | struct Properties 87 | { 88 | Properties() : mode(ModeNotPlayed), color(TangoSkyBlue) { } 89 | 90 | Mode mode; 91 | TrackColor color; 92 | }; 93 | 94 | }; // end namespace 95 | 96 | #endif 97 | -------------------------------------------------------------------------------- /src/libmidi/MidiTrack.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __MIDI_TRACK_H 6 | #define __MIDI_TRACK_H 7 | 8 | #include 9 | #include 10 | 11 | #include "Note.h" 12 | #include "MidiEvent.h" 13 | #include "MidiUtil.h" 14 | 15 | class MidiEvent; 16 | 17 | typedef std::vector MidiEventList; 18 | typedef std::vector MidiEventPulsesList; 19 | typedef std::vector MidiEventMicrosecondList; 20 | 21 | class MidiTrack 22 | { 23 | public: 24 | static MidiTrack ReadFromStream(std::istream &stream); 25 | static MidiTrack CreateBlankTrack() { return MidiTrack(); } 26 | 27 | MidiEventList &Events() { return m_events; } 28 | MidiEventPulsesList &EventPulses() { return m_event_pulses; } 29 | MidiEventMicrosecondList &EventUsecs() { return m_event_usecs; } 30 | 31 | const MidiEventList &Events() const { return m_events; } 32 | const MidiEventPulsesList &EventPulses() const { return m_event_pulses; } 33 | const MidiEventMicrosecondList &EventUsecs() const { return m_event_usecs; } 34 | 35 | void SetEventUsecs(const MidiEventMicrosecondList &event_usecs) { m_event_usecs = event_usecs; } 36 | 37 | const std::wstring InstrumentName() const { return InstrumentNames[m_instrument_id]; } 38 | bool IsPercussion() const { return m_instrument_id == InstrumentIdPercussion; } 39 | 40 | const NoteSet &Notes() const { return m_note_set; } 41 | 42 | void SetTrackId(size_t track_id); 43 | 44 | // Reports whether this track contains any Note-On MIDI events 45 | // (vs. just being an information track with a title or copyright) 46 | bool hasNotes() const { return (m_note_set.size() > 0); } 47 | 48 | void Reset(); 49 | MidiEventList Update(microseconds_t delta_microseconds); 50 | 51 | unsigned int AggregateEventsRemain() const { return static_cast(m_events.size() - (m_last_event + 1)); } 52 | unsigned int AggregateEventCount() const { return static_cast(m_events.size()); } 53 | 54 | unsigned int AggregateNotesRemain() const { return m_notes_remaining; } 55 | unsigned int AggregateNoteCount() const { return static_cast(m_note_set.size()); } 56 | 57 | private: 58 | MidiTrack() : m_instrument_id(0) { Reset(); } 59 | 60 | void BuildNoteSet(); 61 | void DiscoverInstrument(); 62 | 63 | MidiEventList m_events; 64 | MidiEventPulsesList m_event_pulses; 65 | MidiEventMicrosecondList m_event_usecs; 66 | 67 | NoteSet m_note_set; 68 | 69 | int m_instrument_id; 70 | 71 | microseconds_t m_running_microseconds; 72 | long m_last_event; 73 | 74 | unsigned int m_notes_remaining; 75 | }; 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /src/resource.rc: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "resource.h" 6 | 7 | #define APSTUDIO_READONLY_SYMBOLS 8 | #define IDC_STATIC -1 9 | #include 10 | #undef APSTUDIO_READONLY_SYMBOLS 11 | 12 | ///////////////////////////////////////////////////////////////////////////// 13 | // English (U.S.) resources 14 | 15 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 16 | #ifdef _WIN32 17 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 18 | #pragma code_page(1252) 19 | #endif //_WIN32 20 | 21 | // Icon with lowest ID value placed first to ensure application icon 22 | // remains consistent on all systems. 23 | IDI_MAIN_ICON ICON "..\\app_icon.ico" 24 | 25 | title_Logo GRAPHICS "..\\graphics\\title_Logo.tga" 26 | InterfaceButtons GRAPHICS "..\\graphics\\InterfaceButtons.tga" 27 | 28 | score_RetrySong GRAPHICS "..\\graphics\\score_RetrySong.tga" 29 | title_ChooseTracks GRAPHICS "..\\graphics\\title_ChooseTracks.tga" 30 | title_Exit GRAPHICS "..\\graphics\\title_Exit.tga" 31 | tracks_BackToTitle GRAPHICS "..\\graphics\\tracks_BackToTitle.tga" 32 | tracks_PlaySong GRAPHICS "..\\graphics\\tracks_PlaySong.tga" 33 | 34 | title_InputBox GRAPHICS "..\\graphics\\title_InputBox.tga" 35 | title_OutputBox GRAPHICS "..\\graphics\\title_OutputBox.tga" 36 | title_SongBox GRAPHICS "..\\graphics\\title_SongBox.tga" 37 | 38 | trackbox GRAPHICS "..\\graphics\\trackbox.tga" 39 | 40 | stats_text GRAPHICS "..\\graphics\\stats_text.tga" 41 | 42 | play_Status GRAPHICS "..\\graphics\\play_Status.tga" 43 | play_Status2 GRAPHICS "..\\graphics\\play_Status2.tga" 44 | play_Keys GRAPHICS "..\\graphics\\play_Keys.tga" 45 | 46 | play_NotesBlackColor GRAPHICS "..\\graphics\\play_NotesBlackColor.tga" 47 | play_NotesBlackShadow GRAPHICS "..\\graphics\\play_NotesBlackShadow.tga" 48 | play_NotesWhiteColor GRAPHICS "..\\graphics\\play_NotesWhiteColor.tga" 49 | play_NotesWhiteShadow GRAPHICS "..\\graphics\\play_NotesWhiteShadow.tga" 50 | 51 | play_KeyRail GRAPHICS "..\\graphics\\play_KeyRail.tga" 52 | play_KeyShadow GRAPHICS "..\\graphics\\play_KeyShadow.tga" 53 | play_KeysBlack GRAPHICS "..\\graphics\\play_KeysBlack.tga" 54 | 55 | #ifdef APSTUDIO_INVOKED 56 | 57 | 1 TEXTINCLUDE 58 | BEGIN 59 | "resource.h\0" 60 | END 61 | 62 | 2 TEXTINCLUDE 63 | BEGIN 64 | "#define IDC_STATIC -1\r\n" 65 | "#include \r\n" 66 | "\r\n" 67 | "\r\n" 68 | "\0" 69 | END 70 | 71 | 3 TEXTINCLUDE 72 | BEGIN 73 | "\r\n" 74 | "\0" 75 | END 76 | 77 | #endif 78 | 79 | #endif // English (U.S.) resources 80 | ///////////////////////////////////////////////////////////////////////////// 81 | -------------------------------------------------------------------------------- /src/libmidi/MidiComm.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __MIDI_COMM_H 6 | #define __MIDI_COMM_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../os.h" 13 | 14 | #ifndef WIN32 15 | #include 16 | #include 17 | #endif 18 | 19 | #include "MidiEvent.h" 20 | 21 | struct MidiCommDescription 22 | { 23 | unsigned int id; 24 | std::wstring name; 25 | }; 26 | 27 | typedef std::vector MidiCommDescriptionList; 28 | typedef std::queue MidiEventQueue; 29 | 30 | // Once you create a MidiCommIn object, MIDI events are read continuously 31 | // in a separate thread and stored in a buffer. Use the Read() function 32 | // to grab one event at a time from the buffer. 33 | class MidiCommIn 34 | { 35 | public: 36 | static MidiCommDescriptionList GetDeviceList(); 37 | 38 | // device_id is obtained from GetDeviceList() 39 | MidiCommIn(unsigned int device_id); 40 | ~MidiCommIn(); 41 | 42 | MidiCommDescription GetDeviceDescription() const { return m_description; } 43 | 44 | // Returns the next buffered input event. Use KeepReading() (usually in 45 | // a while loop) to see if you should call this function. If called when 46 | // KeepReading() is false, this will throw MidiError_NoInputAvailable. 47 | MidiEvent Read(); 48 | 49 | // Discard events from the input buffer 50 | void Reset(); 51 | 52 | // Returns whether the input device has more buffered events. 53 | bool KeepReading() const; 54 | 55 | // Internal callback, do not use! 56 | // 57 | // NOTE: The Mac implementation of this class uses this callback 58 | // in a different way than Windows. Windows calls this function 59 | // with a variety of Windows data (error messages, structs, and 60 | // whatnot). The Mac side uses the three parameters as the usual 61 | // MIDI event triple. (SysEx is filtered out in both cases.) 62 | void InputCallback(unsigned int msg, unsigned long p1, unsigned long p2); 63 | 64 | private: 65 | MidiCommDescription m_description; 66 | 67 | MidiEventQueue m_event_buffer; 68 | 69 | #ifdef WIN32 70 | HMIDIIN m_input_device; 71 | mutable CRITICAL_SECTION m_buffer_mutex; 72 | #else 73 | MIDIClientRef m_client; 74 | MIDIPortRef m_port; 75 | mutable pthread_mutex_t m_mutex; 76 | #endif 77 | 78 | }; 79 | 80 | class MidiCommOut 81 | { 82 | public: 83 | static MidiCommDescriptionList GetDeviceList(); 84 | 85 | // device_id is obtained from GetDeviceList() 86 | MidiCommOut(unsigned int device_id); 87 | ~MidiCommOut(); 88 | 89 | MidiCommDescription GetDeviceDescription() const { return m_description; } 90 | 91 | // Send a single event out to the device. 92 | void Write(const MidiEvent &out); 93 | 94 | // Turns all notes off and resets all controllers 95 | void Reset(); 96 | 97 | private: 98 | MidiCommDescription m_description; 99 | 100 | #ifdef WIN32 101 | HMIDIOUT m_output_device; 102 | #else 103 | void Acquire(unsigned int device_id); 104 | void Release(); 105 | 106 | MIDIClientRef m_client; 107 | MIDIPortRef m_port; 108 | MIDIEndpointRef m_endpoint; 109 | 110 | AudioUnit m_device; 111 | AudioUnit m_output; 112 | #endif 113 | 114 | }; 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /src/libmidi/Midi.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __MIDI_H 6 | #define __MIDI_H 7 | 8 | #include 9 | #include 10 | 11 | #include "Note.h" 12 | #include "MidiTrack.h" 13 | #include "MidiTypes.h" 14 | 15 | class MidiError; 16 | class MidiEvent; 17 | 18 | typedef std::vector MidiTrackList; 19 | 20 | typedef std::vector MidiEventList; 21 | typedef std::vector > MidiEventListWithTrackId; 22 | 23 | // NOTE: This library's MIDI loading and handling is destructive. Perfect 24 | // 1:1 serialization routines will not be possible without quite a 25 | // bit of additional work. 26 | class Midi 27 | { 28 | public: 29 | static Midi ReadFromFile(const std::wstring &filename); 30 | static Midi ReadFromStream(std::istream &stream); 31 | 32 | const std::vector &Tracks() const { return m_tracks; } 33 | 34 | const TranslatedNoteSet &Notes() const { return m_translated_notes; } 35 | 36 | MidiEventListWithTrackId Update(microseconds_t delta_microseconds); 37 | 38 | void Reset(microseconds_t lead_in_microseconds, microseconds_t lead_out_microseconds); 39 | 40 | microseconds_t GetSongPositionInMicroseconds() const { return m_microsecond_song_position; } 41 | microseconds_t GetSongLengthInMicroseconds() const; 42 | 43 | microseconds_t GetDeadAirStartOffsetMicroseconds() const { return m_microsecond_dead_start_air; } 44 | 45 | // This doesn't include lead-in (so it's perfect for a progress bar). 46 | // (It is also clamped to [0.0, 1.0], so lead-in and lead-out won't give any 47 | // unexpected results.) 48 | double GetSongPercentageComplete() const; 49 | 50 | // This will report when the lead-out period is complete. 51 | bool IsSongOver() const; 52 | 53 | unsigned int AggregateEventsRemain() const; 54 | unsigned int AggregateEventCount() const; 55 | 56 | unsigned int AggregateNotesRemain() const; 57 | unsigned int AggregateNoteCount() const; 58 | 59 | private: 60 | const static unsigned long DefaultBPM = 120; 61 | const static microseconds_t OneMinuteInMicroseconds = 60000000; 62 | const static microseconds_t DefaultUSTempo = OneMinuteInMicroseconds / DefaultBPM; 63 | 64 | static microseconds_t ConvertPulsesToMicroseconds(unsigned long pulses, microseconds_t tempo, unsigned short pulses_per_quarter_note); 65 | 66 | Midi(): m_initialized(false), m_microsecond_dead_start_air(0) { Reset(0, 0); } 67 | 68 | // This is O(n) where n is the number of tempo changes (across all tracks) in 69 | // the song up to the specified time. Tempo changes are usually a small number. 70 | // (Almost always 0 or 1, going up to maybe 30-100 in rare cases.) 71 | microseconds_t GetEventPulseInMicroseconds(unsigned long event_pulses, unsigned short pulses_per_quarter_note) const; 72 | 73 | unsigned long FindFirstNotePulse(); 74 | 75 | void BuildTempoTrack(); 76 | void TranslateNotes(const NoteSet ¬es, unsigned short pulses_per_quarter_note); 77 | 78 | bool m_initialized; 79 | 80 | TranslatedNoteSet m_translated_notes; 81 | 82 | // Position can be negative (for lead-in). 83 | microseconds_t m_microsecond_song_position; 84 | microseconds_t m_microsecond_base_song_length; 85 | 86 | microseconds_t m_microsecond_lead_out; 87 | microseconds_t m_microsecond_dead_start_air; 88 | 89 | bool m_first_update_after_reset; 90 | double m_playback_speed; 91 | MidiTrackList m_tracks; 92 | }; 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /src/libmidi/MidiEvent.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __MIDI_EVENT_H 6 | #define __MIDI_EVENT_H 7 | 8 | #include 9 | #include 10 | 11 | #include "Note.h" 12 | #include "MidiUtil.h" 13 | 14 | struct MidiEventSimple 15 | { 16 | MidiEventSimple() : status(0), byte1(0), byte2(0) { } 17 | MidiEventSimple(unsigned char s, unsigned char b1, unsigned char b2) : status(s), byte1(b1), byte2(b2) { } 18 | 19 | unsigned char status; 20 | unsigned char byte1; 21 | unsigned char byte2; 22 | }; 23 | 24 | class MidiEvent 25 | { 26 | public: 27 | static MidiEvent ReadFromStream(std::istream &stream, unsigned char last_status, bool contains_delta_pulses = true); 28 | static MidiEvent Build(const MidiEventSimple &simple); 29 | static MidiEvent NullEvent(); 30 | 31 | // NOTE: There is a VERY good chance you don't want to use this directly. 32 | // The only reason it's not private is because the standard containers 33 | // require a default constructor. 34 | MidiEvent() : m_status(0), m_data1(0), m_data2(0), m_tempo_uspqn(0) { } 35 | 36 | // Returns true if the event could be expressed in a simple event. (So, this will 37 | // return false for Meta and SysEx events.) 38 | bool GetSimpleEvent(MidiEventSimple *simple) const; 39 | 40 | MidiEventType Type() const; 41 | unsigned long GetDeltaPulses() const { return m_delta_pulses; } 42 | 43 | // This is generally for internal Midi library use only. 44 | void SetDeltaPulses(unsigned long delta_pulses) { m_delta_pulses = delta_pulses; } 45 | 46 | void ShiftNote(int shift_amount); 47 | 48 | NoteId NoteNumber() const; 49 | 50 | // Returns a friendly name for this particular Note-On or Note- 51 | // Off event. (e.g. "A#2") Returns empty string on other types 52 | // of events. 53 | static std::string NoteName(NoteId note_number); 54 | 55 | // Returns the "Program to change to" value if this is a Program 56 | // Change event, 0 otherwise. 57 | int MidiEvent::ProgramNumber() const; 58 | 59 | // Returns the "velocity" of a Note-On (or 0 if this is a Note- 60 | // Off event). Returns -1 for other event types. 61 | int NoteVelocity() const; 62 | 63 | void SetVelocity(int velocity); 64 | 65 | // Returns which type of meta event this is (or 66 | // MetaEvent_Unknown if type() is not EventType_Meta). 67 | MidiMetaEventType MetaType() const; 68 | 69 | // Retrieve the tempo from a tempo meta event in microseconds 70 | // per quarter note. (Non-meta-tempo events will throw an error). 71 | unsigned long GetTempoInUsPerQn() const; 72 | 73 | // Convenience function: Is this the special End-Of-Track event 74 | bool IsEnd() const; 75 | 76 | // Returns which channel this event operates on. This is 77 | // only defined for standard MIDI events that require a 78 | // channel argument. 79 | unsigned char Channel() const; 80 | 81 | void SetChannel(unsigned char channel); 82 | 83 | // Does this event type allow arbitrary text 84 | bool HasText() const; 85 | 86 | // Returns the text content of the event (or empty-string if 87 | // this isn't a text event.) 88 | std::string Text() const; 89 | 90 | // Returns the status code of the MIDI event 91 | unsigned char StatusCode() const { return m_status; } 92 | 93 | private: 94 | void ReadMeta(std::istream &stream); 95 | void ReadSysEx(std::istream &stream); 96 | void ReadStandard(std::istream &stream); 97 | 98 | unsigned char m_status; 99 | unsigned char m_data1; 100 | unsigned char m_data2; 101 | unsigned long m_delta_pulses; 102 | 103 | unsigned char m_meta_type; 104 | 105 | unsigned long m_tempo_uspqn; 106 | std::string m_text; 107 | }; 108 | 109 | 110 | #endif __MIDI_EVENT_H 111 | -------------------------------------------------------------------------------- /src/libmidi/MidiUtil.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __MIDI_UTILS_H 6 | #define __MIDI_UTILS_H 7 | 8 | #include 9 | #include 10 | 11 | // Cross-platform Endian conversion functions 12 | // 13 | // MIDI is big endian. Some platforms aren't 14 | unsigned long BigToSystem32(unsigned long x); 15 | unsigned short BigToSystem16(unsigned short x); 16 | 17 | // MIDI contains these wacky variable length numbers where 18 | // the value is stored only in the first 7 bits of each 19 | // byte, and the last bit is a kind of "keep going" flag. 20 | unsigned long parse_variable_length(std::istream &in); 21 | 22 | const static int InstrumentCount = 130; 23 | const static int InstrumentIdVarious = InstrumentCount - 1; 24 | const static int InstrumentIdPercussion = InstrumentCount - 2; 25 | extern std::wstring const InstrumentNames[InstrumentCount]; 26 | 27 | enum MidiErrorCode 28 | { 29 | MidiError_BadFilename, 30 | MidiError_NoHeader, 31 | MidiError_UnknownHeaderType, 32 | MidiError_BadHeaderSize, 33 | MidiError_Type2MidiNotSupported, 34 | MidiError_BadType0Midi, 35 | MidiError_SMTPETimingNotImplemented, 36 | 37 | MidiError_TrackHeaderTooShort, 38 | MidiError_BadTrackHeaderType, 39 | MidiError_TrackTooShort, 40 | MidiError_BadTrackEnd, 41 | 42 | MidiError_EventTooShort, 43 | MidiError_UnknownEventType, 44 | MidiError_UnknownMetaEventType, 45 | 46 | // MMSYSTEM Errors for MIDI I/O 47 | MidiError_MM_NoDevice, 48 | MidiError_MM_NotEnabled, 49 | MidiError_MM_AlreadyAllocated, 50 | MidiError_MM_BadDeviceID, 51 | MidiError_MM_InvalidParameter, 52 | MidiError_MM_NoDriver, 53 | MidiError_MM_NoMemory, 54 | MidiError_MM_Unknown, 55 | 56 | MidiError_NoInputAvailable, 57 | MidiError_MetaEventOnInput, 58 | 59 | MidiError_InputError, 60 | MidiError_InvalidInputErrorBehavior, 61 | 62 | MidiError_RequestedTempoFromNonTempoEvent 63 | }; 64 | 65 | class MidiError : public std::exception 66 | { 67 | public: 68 | MidiError(MidiErrorCode error) : m_error(error) { } 69 | std::wstring GetErrorDescription() const; 70 | 71 | const MidiErrorCode m_error; 72 | 73 | private: 74 | MidiError operator =(const MidiError&); 75 | }; 76 | 77 | enum MidiEventType 78 | { 79 | MidiEventType_Meta, 80 | MidiEventType_SysEx, 81 | MidiEventType_Unknown, 82 | 83 | MidiEventType_NoteOff, 84 | MidiEventType_NoteOn, 85 | MidiEventType_Aftertouch, 86 | MidiEventType_Controller, 87 | MidiEventType_ProgramChange, 88 | MidiEventType_ChannelPressure, 89 | MidiEventType_PitchWheel 90 | }; 91 | std::wstring GetMidiEventTypeDescription(MidiEventType type); 92 | 93 | enum MidiMetaEventType 94 | { 95 | MidiMetaEvent_SequenceNumber = 0x00, 96 | 97 | MidiMetaEvent_Text = 0x01, 98 | MidiMetaEvent_Copyright = 0x02, 99 | MidiMetaEvent_TrackName = 0x03, 100 | MidiMetaEvent_Instrument = 0x04, 101 | MidiMetaEvent_Lyric = 0x05, 102 | MidiMetaEvent_Marker = 0x06, 103 | MidiMetaEvent_Cue = 0x07, 104 | MidiMetaEvent_PatchName = 0x08, 105 | MidiMetaEvent_DeviceName = 0x09, 106 | 107 | MidiMetaEvent_EndOfTrack = 0x2F, 108 | MidiMetaEvent_TempoChange = 0x51, 109 | MidiMetaEvent_SMPTEOffset = 0x54, 110 | MidiMetaEvent_TimeSignature = 0x58, 111 | MidiMetaEvent_KeySignature = 0x59, 112 | 113 | MidiMetaEvent_Proprietary = 0x7F, 114 | 115 | // Deprecated Meta Events 116 | MidiMetaEvent_ChannelPrefix = 0x20, 117 | MidiMetaEvent_MidiPort = 0x21, 118 | 119 | MidiMetaEvent_Unknown = 0xFF 120 | }; 121 | 122 | // Returns a human-readable description of this meta type 123 | // type type of the text ought to contain in 124 | // this event. (e.g. Copyright, Lyric, Track name, etc.) 125 | // (If this isn't a meta event, returns an empty string) 126 | std::wstring GetMidiMetaEventTypeDescription(MidiMetaEventType type); 127 | 128 | 129 | #endif 130 | -------------------------------------------------------------------------------- /src/TextWriter.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __TEXTWRITER_H 6 | #define __TEXTWRITER_H 7 | 8 | #ifndef __cdecl 9 | #define __cdecl 10 | #endif 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "os.h" 17 | #include "string_util.h" 18 | #include "TrackProperties.h" 19 | 20 | class Renderer; 21 | 22 | // A nice ostream-like class for drawing OS-specific (or OpenGL) text to the 23 | // screen in varying colors, fonts, and sizes. 24 | // 25 | // MACNOTE: Mac version of text-placement routine inserts a bizarre space 26 | // after each successive << operator. 27 | // 28 | class TextWriter 29 | { 30 | public: 31 | // Centering only works for single-write lines... in other words, centered 32 | // lines can only be 1 color. 33 | TextWriter(int in_x, int in_y, Renderer &in_renderer, bool in_centered = false, int in_size = 12, std::wstring fontname = L"Trebuchet MS"); 34 | ~TextWriter(); 35 | 36 | // Skips at least 1 line, or the height of the last write... whichever is greater 37 | // (so that you can skip down past a multiline write) 38 | TextWriter& next_line(); 39 | 40 | // Allow manipulators 41 | TextWriter& operator<<(TextWriter& (__cdecl *_Pfn)(TextWriter&)) 42 | { 43 | (*_Pfn)(*(TextWriter *)this); 44 | return (*this); 45 | } 46 | 47 | private: 48 | TextWriter operator=(const TextWriter&); 49 | TextWriter(const TextWriter&); 50 | 51 | int get_point_size(); 52 | 53 | int point_size; 54 | int x, y, size, original_x; 55 | int last_line_height; 56 | bool centered; 57 | Renderer renderer; 58 | 59 | friend class Text; 60 | }; 61 | 62 | // Some colors to choose from, for convenience 63 | const static Color Black = { 0x00,0x00,0x00, 0xFF }; 64 | const static Color Dk_Blue = { 0xC4,0x00,0x00, 0xFF }; 65 | const static Color Dk_Green = { 0x00,0xC4,0x00, 0xFF }; 66 | const static Color Dk_Cyan = { 0xFF,0x80,0x00, 0xFF }; 67 | const static Color Dk_Red = { 0x00,0x00,0xC4, 0xFF }; 68 | const static Color Dk_Purple = { 0x80,0x00,0x80, 0xFF }; 69 | const static Color Brown = { 0x00,0x40,0x80, 0xFF }; 70 | const static Color Gray = { 0xBB,0xBB,0xBB, 0xFF }; 71 | const static Color Dk_Gray = { 0x55,0x55,0x55, 0xFF }; 72 | const static Color Blue = { 0xFF,0x00,0x00, 0xFF }; 73 | const static Color Green = { 0x00,0xFF,0x00, 0xFF }; 74 | const static Color Cyan = { 0xFF,0xFF,0x00, 0xFF }; 75 | const static Color Red = { 0x00,0x00,0xFF, 0xFF }; 76 | const static Color Magenta = { 0xFF,0x00,0xFF, 0xFF }; 77 | const static Color Yellow = { 0x00,0xFF,0xFF, 0xFF }; 78 | const static Color White = { 0xFF,0xFF,0xFF, 0xFF }; 79 | const static Color Orange = { 0x20,0x80,0xFF, 0xFF }; 80 | const static Color Pink = { 0xA0,0x80,0xFF, 0xFF }; 81 | const static Color CheatYellow = { 0x00,0xCC,0xFF, 0xFF }; 82 | 83 | 84 | // A class to use TextWriter, and write to the screen 85 | class Text 86 | { 87 | public: 88 | Text(std::wstring t, Color color) : m_color(color), m_text(t) { } 89 | Text(int i, Color color) : m_color(color), m_text(WSTRING(i)) { } 90 | Text(double d, int prec, Color color) : m_color(color), m_text(WSTRING(std::setprecision(prec) << d)) { } 91 | 92 | TextWriter& operator<<(TextWriter& tw) const; 93 | 94 | private: 95 | 96 | // This will return where the text should be drawn on 97 | // the screen (determined in an OS dependent way) and 98 | // advance the TextWriter's position by the width and/or 99 | // height of the text. 100 | void calculate_position_and_advance_cursor(TextWriter &tw, int *out_x, int *out_y) const; 101 | 102 | Color m_color; 103 | std::wstring m_text; 104 | }; 105 | 106 | // newline manipulator 107 | TextWriter& newline(TextWriter& tw); 108 | 109 | TextWriter& operator<<(TextWriter& tw, const Text& t); 110 | TextWriter& operator<<(TextWriter& tw, const std::wstring& s); 111 | TextWriter& operator<<(TextWriter& tw, const int& i); 112 | TextWriter& operator<<(TextWriter& tw, const unsigned int& i); 113 | TextWriter& operator<<(TextWriter& tw, const long& l); 114 | TextWriter& operator<<(TextWriter& tw, const unsigned long& l); 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /src/KeyboardDisplay.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __KEYBOARDDISPLAY_H 6 | #define __KEYBOARDDISPLAY_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "TrackTile.h" 13 | #include "TrackProperties.h" 14 | 15 | #include "libmidi/Note.h" 16 | #include "libmidi/MidiTypes.h" 17 | 18 | enum KeyboardSize 19 | { 20 | KeyboardSize37, 21 | KeyboardSize49, 22 | KeyboardSize61, 23 | KeyboardSize76, 24 | KeyboardSize88 25 | }; 26 | 27 | 28 | typedef std::map KeyNames; 29 | 30 | class Renderer; 31 | class Tga; 32 | 33 | class KeyboardDisplay 34 | { 35 | public: 36 | const static microseconds_t NoteWindowLength = 330000; 37 | 38 | KeyboardDisplay(KeyboardSize size, int pixelWidth, int pixelHeight); 39 | 40 | void Draw(Renderer &renderer, const Tga *key_tex[3], const Tga *note_tex[4], int x, int y, 41 | const TranslatedNoteSet ¬es, microseconds_t show_duration, microseconds_t current_time, 42 | const std::vector &track_properties); 43 | 44 | void SetKeyActive(const std::string &key_name, bool active, Track::TrackColor color); 45 | 46 | void ResetActiveKeys() { m_active_keys.clear(); } 47 | 48 | private: 49 | 50 | struct NoteTexDimensions 51 | { 52 | int tex_width; 53 | int tex_height; 54 | 55 | int left; 56 | int right; 57 | 58 | int crown_start; 59 | int crown_end; 60 | 61 | int heel_start; 62 | int heel_end; 63 | }; 64 | const static NoteTexDimensions WhiteNoteDimensions; 65 | const static NoteTexDimensions BlackNoteDimensions; 66 | 67 | struct KeyTexDimensions 68 | { 69 | int tex_width; 70 | int tex_height; 71 | 72 | int left; 73 | int right; 74 | 75 | int top; 76 | int bottom; 77 | }; 78 | const static KeyTexDimensions BlackKeyDimensions; 79 | 80 | 81 | void DrawWhiteKeys(Renderer &renderer, bool active_only, int key_count, int key_width, int key_height, 82 | int key_space, int x_offset, int y_offset) const; 83 | 84 | void DrawBlackKeys(Renderer &renderer, const Tga *tex, bool active_only,int white_key_count, int white_width, 85 | int black_width, int black_height, int key_space, int x_offset, int y_offset, int black_offset) const; 86 | 87 | void DrawRail(Renderer &renderer, const Tga *tex, int x, int y, int width) const; 88 | void DrawShadow(Renderer &renderer, const Tga *tex, int x, int y, int width) const; 89 | 90 | void DrawGuides(Renderer &renderer, int key_count, int key_width, int key_space, 91 | int x_offset, int y, int y_offset) const; 92 | 93 | void DrawNotePass(Renderer &renderer, const Tga *tex_white, const Tga *tex_black, int white_width, 94 | int key_space, int black_width, int black_offset, int x_offset, int y, int y_offset, int y_roll_under, 95 | const TranslatedNoteSet ¬es, microseconds_t show_duration, microseconds_t current_time, 96 | const std::vector &track_properties) const; 97 | 98 | // This takes the rectangle where the actual note block should appear and transforms 99 | // it to the multi-quad (with relatively complicated texture coordinates) using the 100 | // passed-in texture descriptor, and then draws the result 101 | void DrawNote(Renderer &renderer, const Tga *tex, const NoteTexDimensions &tex_dimensions, int x, int y, int w, int h, int color_id) const; 102 | 103 | // This works very much like DrawNote 104 | void DrawBlackKey(Renderer &renderer, const Tga *tex, const KeyTexDimensions &tex_dimensions, int x, int y, int w, int h, Track::TrackColor color) const; 105 | 106 | // Retrieves which white-key a piano with the given key count 107 | // will start with on the far left side 108 | char GetStartingNote() const; 109 | 110 | // Retrieves which octave a piano with the given key count 111 | // will start with on the far left side 112 | int GetStartingOctave() const; 113 | 114 | // Retrieves the number of white keys a piano with the given 115 | // key count will contain 116 | int GetWhiteKeyCount() const; 117 | 118 | KeyboardSize m_size; 119 | KeyNames m_active_keys; 120 | 121 | int m_width; 122 | int m_height; 123 | }; 124 | 125 | #endif 126 | -------------------------------------------------------------------------------- /src/DeviceTile.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "DeviceTile.h" 6 | #include "TextWriter.h" 7 | #include "Renderer.h" 8 | #include "Tga.h" 9 | 10 | const static int GraphicWidth = 36; 11 | const static int GraphicHeight = 36; 12 | 13 | DeviceTile::DeviceTile(int x, int y, int device_id, DeviceTileType type, 14 | const MidiCommDescriptionList &device_list, 15 | Tga *button_graphics, Tga *frame_graphics) 16 | : m_x(x), m_y(y), m_device_id(device_id), m_preview_on(false), m_tile_type(type), 17 | m_device_list(device_list), m_button_graphics(button_graphics), 18 | m_frame_graphics(frame_graphics) 19 | { 20 | // Initialize the size and position of each button 21 | whole_tile = ButtonState(0, 0, DeviceTileWidth, DeviceTileHeight); 22 | button_mode_left = ButtonState( 6, 38, GraphicWidth, GraphicHeight); 23 | button_mode_right = ButtonState(428, 38, GraphicWidth, GraphicHeight); 24 | button_preview = ButtonState(469, 38, GraphicWidth, GraphicHeight); 25 | } 26 | 27 | void DeviceTile::Update(const MouseInfo &translated_mouse) 28 | { 29 | // Update the mouse state of each button 30 | whole_tile.Update(translated_mouse); 31 | button_preview.Update(translated_mouse); 32 | button_mode_left.Update(translated_mouse); 33 | button_mode_right.Update(translated_mouse); 34 | 35 | if (m_device_list.size() > 0) 36 | { 37 | const int last_device = static_cast(m_device_list.size() - 1); 38 | 39 | if (button_mode_left.hit) 40 | { 41 | if (m_device_id == -1) m_device_id = last_device; 42 | else --m_device_id; 43 | } 44 | 45 | if (button_mode_right.hit) 46 | { 47 | if (m_device_id == last_device) m_device_id = -1; 48 | else ++m_device_id; 49 | } 50 | } 51 | 52 | if (button_preview.hit) 53 | { 54 | m_preview_on = !m_preview_on; 55 | } 56 | 57 | } 58 | 59 | int DeviceTile::LookupGraphic(TrackTileGraphic graphic, bool button_hovering) const 60 | { 61 | // There are three sets of graphics 62 | // set 0: window lit, hovering 63 | // set 1: window lit, not-hovering 64 | // set 2: window unlit, (implied not-hovering) 65 | int graphic_set = 2; 66 | if (whole_tile.hovering) graphic_set--; 67 | if (button_hovering) graphic_set--; 68 | 69 | const int set_offset = GraphicWidth * Graphic_COUNT; 70 | const int graphic_offset = GraphicWidth * graphic; 71 | 72 | return (set_offset * graphic_set) + graphic_offset; 73 | } 74 | 75 | void DeviceTile::Draw(Renderer &renderer) const 76 | { 77 | renderer.SetOffset(m_x, m_y); 78 | 79 | const Color hover = Renderer::ToColor(0xFF,0xFF,0xFF); 80 | const Color no_hover = Renderer::ToColor(0xE0,0xE0,0xE0); 81 | renderer.SetColor(whole_tile.hovering ? hover : no_hover); 82 | renderer.DrawTga(m_frame_graphics, 0, 0); 83 | 84 | // Choose the last (gray) color in the TrackTile bitmap 85 | int color_offset = GraphicHeight * Track::UserSelectableColorCount; 86 | 87 | renderer.DrawTga(m_button_graphics, BUTTON_RECT(button_mode_left), LookupGraphic(GraphicLeftArrow, button_mode_left.hovering), color_offset); 88 | renderer.DrawTga(m_button_graphics, BUTTON_RECT(button_mode_right), LookupGraphic(GraphicRightArrow, button_mode_right.hovering), color_offset); 89 | 90 | TrackTileGraphic preview_graphic = GraphicPreviewTurnOn; 91 | if (m_preview_on) preview_graphic = GraphicPreviewTurnOff; 92 | renderer.DrawTga(m_button_graphics, BUTTON_RECT(button_preview), LookupGraphic(preview_graphic, button_preview.hovering), color_offset); 93 | 94 | // Draw mode text 95 | TextWriter mode(44, 46, renderer, false, 14); 96 | if (m_device_list.size() == 0) 97 | { 98 | mode << L"[No Devices Found]"; 99 | } 100 | else 101 | { 102 | // A -1 for device_id means "disabled" 103 | if (m_device_id >= 0) 104 | { 105 | mode << m_device_list[m_device_id].name; 106 | } 107 | else 108 | { 109 | switch (m_tile_type) 110 | { 111 | case DeviceTileOutput: mode << L"[Output Off: Display only with no audio]"; break; 112 | case DeviceTileInput: mode << L"[Input Off: Play along with no scoring]"; break; 113 | } 114 | } 115 | } 116 | 117 | renderer.ResetOffset(); 118 | } 119 | 120 | -------------------------------------------------------------------------------- /src/string_util.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __STRING_UTIL_H 6 | #define __STRING_UTIL_H 7 | 8 | // Handy string macros 9 | 10 | #ifndef STRING 11 | #include 12 | #define STRING(v) ((static_cast(std::ostringstream().flush() << v)).str()) 13 | #endif 14 | 15 | #ifndef WSTRING 16 | #include 17 | #define WSTRING(v) ((static_cast(std::wostringstream().flush() << v)).str()) 18 | #endif 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | 28 | 29 | #ifndef WIN32 30 | 31 | #include 32 | 33 | // Helper class to avoid all the CFRelease nonsense. Of course, this means you 34 | // MUST use the converted string in the same scope. It's easy to forget with 35 | // temporaries. 36 | // 37 | // SomeMacFunctionCall(MacStringFromWide(L"woo!").get()); // OK 38 | // 39 | // CFStringRef s = MacStringFromWide(L"woo!").get(); // BAD! Auto-released right away! 40 | // CFStringRef s = MacStringFromWide(L"woo!", true), get(); // OK, but have to CFRelease(s) later. 41 | // 42 | class MacStringFromWide 43 | { 44 | public: 45 | MacStringFromWide(const std::wstring &wide, bool keep_around = false) : cf(0), m_keep_around(keep_around) 46 | { 47 | // TODO: This isn't Unicode! 48 | std::string narrow(wide.begin(), wide.end()); 49 | 50 | cf = CFStringCreateWithCString(0, narrow.c_str(), kCFStringEncodingMacRoman); 51 | } 52 | 53 | CFStringRef get() const 54 | { 55 | return cf; 56 | } 57 | 58 | ~MacStringFromWide() 59 | { 60 | if (!m_keep_around) CFRelease(cf); 61 | } 62 | 63 | private: 64 | CFStringRef cf; 65 | bool m_keep_around; 66 | }; 67 | 68 | static std::wstring WideFromMacString(CFStringRef cf) 69 | { 70 | size_t length = CFStringGetLength(cf) + 1; 71 | char *buffer = (char*)malloc(length); 72 | 73 | Boolean ret = CFStringGetCString(cf, buffer, length, 0); 74 | if (!ret) return std::wstring(); 75 | 76 | std::string narrow(buffer); 77 | return std::wstring(narrow.begin(), narrow.end()); 78 | } 79 | 80 | #endif 81 | 82 | 83 | // string_type here can be things like std::string or std::wstring 84 | template 85 | const string_type StringLower(string_type s) 86 | { 87 | std::locale loc; 88 | 89 | std::transform( s.begin(), s.end(), s.begin(), 90 | std::bind1st( std::mem_fun( &std::ctype::tolower ), 91 | &std::use_facet< std::ctype >( loc ) ) ); 92 | 93 | return s; 94 | } 95 | 96 | // E here is usually wchar_t 97 | template, class A = std::allocator > 98 | class Widen : public std::unary_function< const std::string&, std::basic_string > 99 | { 100 | public: 101 | Widen(const std::locale& loc = std::locale()) : loc_(loc) 102 | { 103 | #if defined(_MSC_VER) && (_MSC_VER < 1300) // VC++ 6.0... 104 | using namespace std; 105 | pCType_ = &_USE(loc, ctype ); 106 | #else 107 | pCType_ = &std::use_facet >(loc); 108 | #endif 109 | } 110 | 111 | std::basic_string operator() (const std::string& str) const 112 | { 113 | if (str.length() == 0) return std::basic_string(); 114 | 115 | typename std::basic_string::size_type srcLen = 116 | str.length(); 117 | const char* pSrcBeg = str.c_str(); 118 | std::vector tmp(srcLen); 119 | 120 | // Visual C++ 2005 has deprecated several Standard C++ Library 121 | // functions that aren't actually deprecated in the standard. 122 | // 123 | // std::ctype<>::widen is one of them. "suppress" only stops 124 | // the warning for the very next line. 125 | #ifdef WIN32 126 | #pragma warning(suppress : 4996) 127 | #endif 128 | 129 | pCType_->widen(pSrcBeg, pSrcBeg + srcLen, &tmp[0]); 130 | return std::basic_string(&tmp[0], srcLen); 131 | } 132 | 133 | private: 134 | std::locale loc_; 135 | const std::ctype* pCType_; 136 | 137 | // No copy-constructor or no assignment operator 138 | Widen(const Widen&); 139 | Widen& operator= (const Widen&); 140 | }; 141 | 142 | 143 | 144 | #endif 145 | -------------------------------------------------------------------------------- /nsis_installer_script.nsi: -------------------------------------------------------------------------------- 1 | !include "MUI.nsh" 2 | 3 | !define VERSION 0.6.1 4 | !define PROJECT_NAME PianoGame 5 | 6 | Name "${PROJECT_NAME} ${VERSION}" 7 | OutFile "${PROJECT_NAME}-${VERSION}-installer.exe" 8 | InstallDir "$PROGRAMFILES\${PROJECT_NAME}" 9 | BrandingText " " 10 | 11 | !define MUI_ABORTWARNING 12 | !define MUI_COMPONENTSPAGE_SMALLDESC 13 | 14 | !insertmacro MUI_PAGE_LICENSE "license.txt" 15 | !insertmacro MUI_PAGE_COMPONENTS 16 | !insertmacro MUI_PAGE_DIRECTORY 17 | !insertmacro MUI_PAGE_INSTFILES 18 | 19 | !insertmacro MUI_UNPAGE_CONFIRM 20 | !insertmacro MUI_UNPAGE_INSTFILES 21 | 22 | !insertmacro MUI_LANGUAGE "English" 23 | 24 | ; Registry key to check for directory (so if you install again, it will 25 | ; overwrite the old one automatically) 26 | InstallDirRegKey HKLM SOFTWARE\${PROJECT_NAME} "Install_Dir" 27 | 28 | ComponentText "This will install ${PROJECT_NAME} ${VERSION} to your computer." 29 | DirText "Choose a directory to install in to:" 30 | 31 | 32 | 33 | Section "!${PROJECT_NAME}" main_application 34 | SectionIn RO 35 | SetOutPath $INSTDIR 36 | 37 | File "Release\${PROJECT_NAME}.exe" 38 | File "readme.txt" 39 | File "license.txt" 40 | 41 | ; Write the installation path into the registry 42 | WriteRegStr HKLM SOFTWARE\${PROJECT_NAME} "Install_Dir" "$INSTDIR" 43 | 44 | ; Write the uninstall keys for Windows 45 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}" "DisplayName" "${PROJECT_NAME} (remove only)" 46 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}" "UninstallString" '"$INSTDIR\uninstall.exe"' 47 | WriteUninstaller "uninstall.exe" 48 | SectionEnd 49 | 50 | 51 | 52 | Section "Start Menu Shortcuts" ShortcutMenu 53 | CreateDirectory "$SMPROGRAMS\${PROJECT_NAME}" 54 | CreateShortCut "$SMPROGRAMS\${PROJECT_NAME}\Play ${PROJECT_NAME}.lnk" "$INSTDIR\${PROJECT_NAME}.exe" "" "$INSTDIR\${PROJECT_NAME}.exe" 0 55 | CreateShortCut "$SMPROGRAMS\${PROJECT_NAME}\View Readme.lnk" "$INSTDIR\readme.txt" 56 | CreateShortCut "$SMPROGRAMS\${PROJECT_NAME}\View License.lnk" "$INSTDIR\license.txt" 57 | CreateShortCut "$SMPROGRAMS\${PROJECT_NAME}\Visit the ${PROJECT_NAME} Website.lnk" "http://www.synthesiagame.com/" 58 | CreateShortCut "$SMPROGRAMS\${PROJECT_NAME}\Uninstall ${PROJECT_NAME}.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 59 | SectionEnd 60 | 61 | 62 | 63 | Section "Right-click Association" Association 64 | WriteRegStr HKCR "MIDFile\shell\Play in ${PROJECT_NAME}\command" "" "$\"$INSTDIR\${PROJECT_NAME}.exe$\" $\"%1$\"" 65 | SectionEnd 66 | 67 | 68 | 69 | Section /o "Desktop Icon" DesktopIcon 70 | CreateShortCut "$DESKTOP\Play ${PROJECT_NAME}.lnk" "$INSTDIR\${PROJECT_NAME}.exe" "" "$INSTDIR\${PROJECT_NAME}.exe" 0 71 | SectionEnd 72 | 73 | 74 | 75 | UninstallText "This will uninstall ${PROJECT_NAME} ${VERSION}. Click next to continue." 76 | Section "Uninstall" 77 | 78 | ; remove registry keys 79 | ; 80 | ; NOTE: this intentionally leaves the "Install_Dir" 81 | ; entry in HKLM\SOFTWARE\[program-name] in the event of 82 | ; subsequent reinstalls/upgrades 83 | ; 84 | DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}" 85 | DeleteRegKey HKCU "SOFTWARE\${PROJECT_NAME}" 86 | 87 | ; delete program files 88 | Delete $INSTDIR\readme.txt 89 | Delete $INSTDIR\license.txt 90 | Delete $INSTDIR\uninstall.exe 91 | Delete "$INSTDIR\${PROJECT_NAME}.exe" 92 | RMDir /r "$INSTDIR" 93 | 94 | ; this won't delete the directory if the user has added anything 95 | RMDir "$DOCUMENTS\${PROJECT_NAME} Music" 96 | 97 | ; remove Start Menu shortcuts 98 | Delete "$SMPROGRAMS\${PROJECT_NAME}\*.*" 99 | RMDir "$SMPROGRAMS\${PROJECT_NAME}" 100 | 101 | ; remove Desktop shortcut 102 | Delete "$DESKTOP\Play ${PROJECT_NAME}.lnk" 103 | 104 | ; remove File Association 105 | DeleteRegKey HKCR "MIDFile\shell\Play in ${PROJECT_NAME}" 106 | 107 | SectionEnd 108 | 109 | 110 | 111 | 112 | LangString DESC_main_application ${LANG_ENGLISH} "Install the ${PROJECT_NAME} application files (required)." 113 | LangString DESC_ShortcutMenu ${LANG_ENGLISH} "Create a ${PROJECT_NAME} start menu group on the 'All Programs' section of your start menu." 114 | LangString DESC_Association ${LANG_ENGLISH} "Add a right-click 'Play in ${PROJECT_NAME}' file association to MIDI files." 115 | LangString DESC_DesktopIcon ${LANG_ENGLISH} "Create a ${PROJECT_NAME} icon on your Windows Desktop." 116 | 117 | !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN 118 | !insertmacro MUI_DESCRIPTION_TEXT ${main_application} $(DESC_main_application) 119 | !insertmacro MUI_DESCRIPTION_TEXT ${ShortcutMenu} $(DESC_ShortcutMenu) 120 | !insertmacro MUI_DESCRIPTION_TEXT ${Association} $(DESC_Association) 121 | !insertmacro MUI_DESCRIPTION_TEXT ${DesktopIcon} $(DESC_DesktopIcon) 122 | !insertmacro MUI_FUNCTION_DESCRIPTION_END 123 | -------------------------------------------------------------------------------- /src/TrackTile.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "TrackTile.h" 6 | #include "libmidi/Midi.h" 7 | #include "Renderer.h" 8 | #include "Tga.h" 9 | 10 | const static int GraphicWidth = 36; 11 | const static int GraphicHeight = 36; 12 | 13 | TrackTile::TrackTile(int x, int y, size_t track_id, Track::TrackColor color, Track::Mode mode) 14 | : m_x(x), m_y(y), m_track_id(track_id), m_color(color), m_mode(mode), m_preview_on(false) 15 | { 16 | // Initialize the size and position of each button 17 | whole_tile = ButtonState(0, 0, TrackTileWidth, TrackTileHeight); 18 | button_mode_left = ButtonState( 2, 68, GraphicWidth, GraphicHeight); 19 | button_mode_right = ButtonState(192, 68, GraphicWidth, GraphicHeight); 20 | button_color = ButtonState(228, 68, GraphicWidth, GraphicHeight); 21 | button_preview = ButtonState(264, 68, GraphicWidth, GraphicHeight); 22 | } 23 | 24 | void TrackTile::Update(const MouseInfo &translated_mouse) 25 | { 26 | // Update the mouse state of each button 27 | whole_tile.Update(translated_mouse); 28 | button_preview.Update(translated_mouse); 29 | button_color.Update(translated_mouse); 30 | button_mode_left.Update(translated_mouse); 31 | button_mode_right.Update(translated_mouse); 32 | 33 | if (button_mode_left.hit) 34 | { 35 | int mode = static_cast(m_mode) - 1; 36 | if (mode < 0) mode = 3; 37 | 38 | m_mode = static_cast(mode); 39 | } 40 | 41 | if (button_mode_right.hit) 42 | { 43 | int mode = static_cast(m_mode) + 1; 44 | if (mode > 3) mode = 0; 45 | 46 | m_mode = static_cast(mode); 47 | } 48 | 49 | if (button_preview.hit) 50 | { 51 | m_preview_on = !m_preview_on; 52 | } 53 | 54 | if (button_color.hit && m_mode != Track::ModeNotPlayed && m_mode != Track::ModePlayedButHidden) 55 | { 56 | int color = static_cast(m_color) + 1; 57 | if (color >= Track::UserSelectableColorCount) color = 0; 58 | 59 | m_color = static_cast(color); 60 | } 61 | 62 | } 63 | 64 | int TrackTile::LookupGraphic(TrackTileGraphic graphic, bool button_hovering) const 65 | { 66 | // There are three sets of graphics 67 | // set 0: window lit, hovering 68 | // set 1: window lit, not-hovering 69 | // set 2: window unlit, (implied not-hovering) 70 | int graphic_set = 2; 71 | if (whole_tile.hovering) graphic_set--; 72 | if (button_hovering) graphic_set--; 73 | 74 | const int set_offset = GraphicWidth * Graphic_COUNT; 75 | const int graphic_offset = GraphicWidth * graphic; 76 | 77 | return (set_offset * graphic_set) + graphic_offset; 78 | } 79 | 80 | void TrackTile::Draw(Renderer &renderer, const Midi *midi, Tga *buttons, Tga *box) const 81 | { 82 | const MidiTrack &track = midi->Tracks()[m_track_id]; 83 | 84 | bool gray_out_buttons = false; 85 | Color light = Track::ColorNoteWhite[m_color]; 86 | Color medium = Track::ColorNoteBlack[m_color]; 87 | 88 | if (m_mode == Track::ModePlayedButHidden || m_mode == Track::ModeNotPlayed) 89 | { 90 | gray_out_buttons = true; 91 | light = Renderer::ToColor(0xB0,0xB0,0xB0); 92 | medium = Renderer::ToColor(0x70,0x70,0x70); 93 | } 94 | 95 | Color color_tile = medium; 96 | Color color_tile_hovered = light; 97 | 98 | renderer.SetOffset(m_x, m_y); 99 | 100 | renderer.SetColor(whole_tile.hovering ? color_tile_hovered : color_tile); 101 | renderer.DrawTga(box, -10, -6); 102 | 103 | renderer.SetColor(White); 104 | 105 | // Write song info to the tile 106 | TextWriter instrument(95, 12, renderer, false, 14); 107 | instrument << track.InstrumentName(); 108 | TextWriter note_count(95, 33, renderer, false, 14); 109 | note_count << static_cast(track.Notes().size()); 110 | 111 | int color_offset = GraphicHeight * static_cast(m_color); 112 | if (gray_out_buttons) color_offset = GraphicHeight * Track::UserSelectableColorCount; 113 | 114 | renderer.DrawTga(buttons, BUTTON_RECT(button_mode_left), LookupGraphic(GraphicLeftArrow, button_mode_left.hovering), color_offset); 115 | renderer.DrawTga(buttons, BUTTON_RECT(button_mode_right), LookupGraphic(GraphicRightArrow, button_mode_right.hovering), color_offset); 116 | renderer.DrawTga(buttons, BUTTON_RECT(button_color), LookupGraphic(GraphicColor, button_color.hovering), color_offset); 117 | 118 | TrackTileGraphic preview_graphic = GraphicPreviewTurnOn; 119 | if (m_preview_on) preview_graphic = GraphicPreviewTurnOff; 120 | renderer.DrawTga(buttons, BUTTON_RECT(button_preview), LookupGraphic(preview_graphic, button_preview.hovering), color_offset); 121 | 122 | // Draw mode text 123 | TextWriter mode(42, 76, renderer, false, 14); 124 | mode << Track::ModeText[m_mode]; 125 | 126 | renderer.ResetOffset(); 127 | } 128 | 129 | -------------------------------------------------------------------------------- /src/Renderer.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "Renderer.h" 6 | #include "Tga.h" 7 | #include "os_graphics.h" 8 | 9 | #include 10 | 11 | 12 | // These are static because OpenGL is (essentially) static 13 | static unsigned int last_texture_id = std::numeric_limits::max(); 14 | void SelectTexture(unsigned int texture_id) 15 | { 16 | if (texture_id == last_texture_id) return; 17 | 18 | glBindTexture(GL_TEXTURE_2D, texture_id); 19 | last_texture_id = texture_id; 20 | } 21 | 22 | 23 | Renderer::Renderer(Context context) : m_context(context), m_xoffset(0), m_yoffset(0) 24 | { 25 | } 26 | 27 | Color Renderer::ToColor(int r, int g, int b, int a) 28 | { 29 | Color c; 30 | c.r = r; 31 | c.g = g; 32 | c.b = b; 33 | c.a = a; 34 | 35 | return c; 36 | } 37 | 38 | void Renderer::SetVSyncInterval(int interval) 39 | { 40 | #ifdef WIN32 41 | 42 | const char *extensions = reinterpret_cast(static_cast(glGetString( GL_EXTENSIONS ))); 43 | 44 | // Check if the WGL_EXT_swap_control extension is supported. 45 | if (strstr(extensions, "WGL_EXT_swap_control") == 0) return; 46 | 47 | typedef BOOL (APIENTRY *SWAP_INTERVAL_PROC)( int ); 48 | SWAP_INTERVAL_PROC wglSwapIntervalEXT = (SWAP_INTERVAL_PROC)wglGetProcAddress( "wglSwapIntervalEXT" ); 49 | if (wglSwapIntervalEXT) wglSwapIntervalEXT(interval); 50 | 51 | #else 52 | 53 | GLint i = interval; 54 | GLboolean ret = aglSetInteger(m_context, AGL_SWAP_INTERVAL, &i); 55 | if (ret == GL_FALSE) 56 | { 57 | // LOGTODO! 58 | // This is non-critical. V-Sync might just not be supported. 59 | } 60 | 61 | #endif 62 | } 63 | 64 | void Renderer::SwapBuffers() 65 | { 66 | #ifdef WIN32 67 | ::SwapBuffers(m_context); 68 | #else 69 | aglSwapBuffers(m_context); 70 | #endif 71 | } 72 | 73 | void Renderer::ForceTexture(unsigned int texture_id) 74 | { 75 | last_texture_id = std::numeric_limits::max(); 76 | SelectTexture(texture_id); 77 | } 78 | 79 | void Renderer::SetColor(Color c) 80 | { 81 | SetColor(c.r, c.g, c.b, c.a); 82 | } 83 | 84 | void Renderer::SetColor(int r, int g, int b, int a) 85 | { 86 | glColor4f(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f); 87 | } 88 | 89 | void Renderer::DrawQuad(int x, int y, int w, int h) 90 | { 91 | SelectTexture(0); 92 | 93 | glBegin(GL_QUADS); 94 | glVertex3i( x + m_xoffset, y + m_yoffset, 0); 95 | glVertex3i( x+w + m_xoffset, y + m_yoffset, 0); 96 | glVertex3i( x+w + m_xoffset, y+h + m_yoffset, 0); 97 | glVertex3i( x + m_xoffset, y+h + m_yoffset, 0); 98 | glEnd(); 99 | } 100 | 101 | void Renderer::DrawTga(const Tga *tga, int x, int y) const 102 | { 103 | DrawTga(tga, x, y, (int)tga->GetWidth(), (int)tga->GetHeight(), 0, 0); 104 | } 105 | 106 | void Renderer::DrawTga(const Tga *tga, int in_x, int in_y, int width, int height, int src_x, int src_y) const 107 | { 108 | const int x = in_x + m_xoffset; 109 | const int y = in_y + m_yoffset; 110 | 111 | const double tx = static_cast(src_x) / static_cast(tga->GetWidth()); 112 | const double ty = -static_cast(src_y) / static_cast(tga->GetHeight()); 113 | const double tw = static_cast(width) / static_cast(tga->GetWidth()); 114 | const double th = -static_cast(height)/ static_cast(tga->GetHeight()); 115 | 116 | SelectTexture(tga->GetId()); 117 | 118 | glBegin(GL_QUADS); 119 | glTexCoord2d( tx, ty); glVertex3i( x, y, 0); 120 | glTexCoord2d( tx, ty+th); glVertex3i( x, y+height, 0); 121 | glTexCoord2d(tx+tw, ty+th); glVertex3i(x+width, y+height, 0); 122 | glTexCoord2d(tx+tw, ty); glVertex3i(x+width, y, 0); 123 | glEnd(); 124 | } 125 | 126 | void Renderer::DrawStretchedTga(const Tga *tga, int x, int y, int w, int h) const 127 | { 128 | DrawStretchedTga(tga, x, y, w, h, 0, 0, (int)tga->GetWidth(), (int)tga->GetHeight()); 129 | } 130 | 131 | void Renderer::DrawStretchedTga(const Tga *tga, int x, int y, int w, int h, int src_x, int src_y, int src_w, int src_h) const 132 | { 133 | const int sx = x + m_xoffset; 134 | const int sy = y + m_yoffset; 135 | 136 | const double tx = static_cast(src_x) / static_cast(tga->GetWidth()); 137 | const double ty = -static_cast(src_y) / static_cast(tga->GetHeight()); 138 | const double tw = static_cast(src_w) / static_cast(tga->GetWidth()); 139 | const double th = -static_cast(src_h) / static_cast(tga->GetHeight()); 140 | 141 | SelectTexture(tga->GetId()); 142 | 143 | glBegin(GL_QUADS); 144 | glTexCoord2d( tx, ty); glVertex3i( sx, sy, 0); 145 | glTexCoord2d( tx, ty+th); glVertex3i( sx, sy+h, 0); 146 | glTexCoord2d(tx+tw, ty+th); glVertex3i(sx+w, sy+h, 0); 147 | glTexCoord2d(tx+tw, ty); glVertex3i(sx+w, sy, 0); 148 | glEnd(); 149 | } 150 | -------------------------------------------------------------------------------- /src/State_Stats.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "State_Stats.h" 6 | #include "State_TrackSelection.h" 7 | #include "State_Playing.h" 8 | #include "Renderer.h" 9 | #include "Textures.h" 10 | 11 | #include 12 | 13 | using namespace std; 14 | 15 | void StatsState::Init() 16 | { 17 | m_back_button = ButtonState(Layout::ScreenMarginX, 18 | GetStateHeight() - Layout::ScreenMarginY/2 - Layout::ButtonHeight/2, 19 | Layout::ButtonWidth, Layout::ButtonHeight); 20 | 21 | m_continue_button = ButtonState(GetStateWidth() - Layout::ScreenMarginX - Layout::ButtonWidth, 22 | GetStateHeight() - Layout::ScreenMarginY/2 - Layout::ButtonHeight/2, 23 | Layout::ButtonWidth, Layout::ButtonHeight); 24 | } 25 | 26 | void StatsState::Update() 27 | { 28 | MouseInfo mouse = Mouse(); 29 | 30 | m_continue_button.Update(mouse); 31 | m_back_button.Update(mouse); 32 | 33 | if (IsKeyPressed(KeyEscape) || m_back_button.hit) 34 | { 35 | ChangeState(new TrackSelectionState(m_state)); 36 | return; 37 | } 38 | 39 | if (IsKeyPressed(KeyEnter) || m_continue_button.hit) 40 | { 41 | ChangeState(new PlayingState(m_state)); 42 | return; 43 | } 44 | 45 | m_tooltip = L""; 46 | if (m_back_button.hovering) m_tooltip = L"Return to the track selection screen."; 47 | if (m_continue_button.hovering) m_tooltip = L"Try this song again with the same settings."; 48 | } 49 | 50 | void StatsState::Draw(Renderer &renderer) const 51 | { 52 | const bool ConstrainedHeight = (GetStateHeight() < 720); 53 | 54 | int left = GetStateWidth() / 2 + 40; 55 | const int InstructionsY = ConstrainedHeight ? 120 : 263; 56 | 57 | renderer.SetColor(White); 58 | renderer.DrawTga(GetTexture(StatsText), left - 270, InstructionsY - 113); 59 | 60 | if (!ConstrainedHeight) 61 | { 62 | Layout::DrawTitle(renderer, m_state.song_title); 63 | Layout::DrawHorizontalRule(renderer, GetStateWidth(), Layout::ScreenMarginY); 64 | } 65 | 66 | Layout::DrawHorizontalRule(renderer, GetStateWidth(), GetStateHeight() - Layout::ScreenMarginY); 67 | 68 | Layout::DrawButton(renderer, m_continue_button, GetTexture(ButtonRetrySong)); 69 | Layout::DrawButton(renderer, m_back_button, GetTexture(ButtonChooseTracks)); 70 | 71 | const SongStatistics &s = m_state.stats; 72 | 73 | double hit_percent = 0.0; 74 | if (s.notes_user_could_have_played > 0) 75 | { 76 | hit_percent = 100.0 * (s.notes_user_actually_played / (s.notes_user_could_have_played * 1.0)); 77 | } 78 | 79 | std::wstring grade = L"F"; 80 | if (hit_percent >= 50) grade = L"D-"; 81 | if (hit_percent >= 55) grade = L"D"; 82 | if (hit_percent >= 63) grade = L"D+"; 83 | if (hit_percent >= 70) grade = L"C-"; 84 | if (hit_percent >= 73) grade = L"C"; 85 | if (hit_percent >= 77) grade = L"C+"; 86 | if (hit_percent >= 80) grade = L"B-"; 87 | if (hit_percent >= 83) grade = L"B"; 88 | if (hit_percent >= 87) grade = L"B+"; 89 | if (hit_percent >= 90) grade = L"A-"; 90 | if (hit_percent >= 93) grade = L"A"; 91 | if (hit_percent >= 97) grade = L"A+"; 92 | if (hit_percent >= 99) grade = L"A++"; 93 | if (hit_percent >= 100) grade = L"A+++"; 94 | 95 | int stray_percent = 0; 96 | if (s.total_notes_user_pressed > 0) stray_percent = static_cast((100.0 * s.stray_notes) / s.total_notes_user_pressed); 97 | 98 | int average_speed = 0; 99 | if (s.notes_user_could_have_played > 0) average_speed = s.speed_integral / s.notes_user_could_have_played; 100 | 101 | // Choose a dynamic color for the grade 102 | const double p = hit_percent / 100.0; 103 | const double r = std::max(0.0, 1 - (p*p*p*p)); 104 | const double g = std::max(0.0, 1 - (((p- 1)*4)*((p- 1)*4))); 105 | const double b = std::max(0.0, 1 - (((p-.75)*5)*((p-.75)*5))); 106 | 107 | const Color c = Renderer::ToColor(int(r*0xFF), int(g*0xFF), int(b*0xFF)); 108 | 109 | TextWriter grade_text(left - 5, InstructionsY - 15, renderer, false, 100); 110 | grade_text << Text(grade, c); 111 | 112 | TextWriter score(left, InstructionsY + 112, renderer, false, 28); 113 | score << WSTRING(static_cast(s.score)); 114 | 115 | TextWriter speed(left, InstructionsY + 147, renderer, false, 28); 116 | speed << WSTRING(average_speed << L" %"); 117 | 118 | TextWriter good(left, InstructionsY + 218, renderer, false, 28); 119 | good << WSTRING(s.notes_user_actually_played << L" / " << s.notes_user_could_have_played << L" (" << static_cast(hit_percent) << L" %" << L")"); 120 | 121 | TextWriter stray(left, InstructionsY + 255, renderer, false, 28); 122 | stray << WSTRING(s.stray_notes << L" (" << stray_percent << L" %" << L")"); 123 | 124 | TextWriter combo(left, InstructionsY + 323, renderer, false, 28); 125 | combo << WSTRING(s.longest_combo); 126 | 127 | 128 | TextWriter tooltip(GetStateWidth() / 2, GetStateHeight() - Layout::ScreenMarginY/2 - Layout::TitleFontSize/2, renderer, true, Layout::TitleFontSize); 129 | tooltip << m_tooltip; 130 | } 131 | -------------------------------------------------------------------------------- /src/registry.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "registry.h" 6 | #include 7 | 8 | using std::wstring; 9 | 10 | Registry::Registry(const RootKey rootKey, const wstring program, const wstring company) 11 | { 12 | good = true; 13 | key = NULL; 14 | 15 | const wstring run_buf = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; 16 | wstring buffer = L"Software\\"; 17 | 18 | if (rootKey != CU_Run && rootKey != LM_Run) 19 | { 20 | if (program.length() > 0) 21 | { 22 | // Handle passing in only one string to write to the company root 23 | if (company.length() > 0) buffer += company + L"\\" + program; 24 | else buffer += program; 25 | } 26 | else good = false; 27 | } 28 | 29 | if (good) 30 | { 31 | long result = 0; 32 | DWORD disposition; 33 | 34 | // Open the requested key 35 | switch(rootKey) 36 | { 37 | case CurrentUser: 38 | result = RegCreateKeyEx(HKEY_CURRENT_USER, buffer.c_str(), 0, L"REG_DWORD", REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, &key, &disposition); 39 | if (result != ERROR_SUCCESS) good = false; 40 | break; 41 | 42 | case LocalMachine: 43 | result = RegCreateKeyEx(HKEY_LOCAL_MACHINE, buffer.c_str(), 0, L"REG_DWORD", REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, &key, &disposition); 44 | if (result != ERROR_SUCCESS) good = false; 45 | break; 46 | 47 | case CU_Run: 48 | result = RegCreateKeyEx(HKEY_CURRENT_USER, run_buf.c_str(), 0, L"REG_DWORD", REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, &key, &disposition); 49 | if (result != ERROR_SUCCESS) good = false; 50 | break; 51 | 52 | case LM_Run: 53 | result = RegCreateKeyEx(HKEY_LOCAL_MACHINE, run_buf.c_str(), 0, L"REG_DWORD", REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, &key, &disposition); 54 | if (result != ERROR_SUCCESS) good = false; 55 | break; 56 | } 57 | } 58 | 59 | assert(good); 60 | } 61 | 62 | Registry::~Registry() 63 | { 64 | RegCloseKey(key); 65 | } 66 | 67 | void Registry::Delete(const wstring keyName) 68 | { 69 | if (!good) return; 70 | RegDeleteValue(key, keyName.c_str()); 71 | } 72 | 73 | 74 | void Registry::Write(const wstring keyName, const wstring value) 75 | { 76 | if (!good) return; 77 | RegSetValueEx(key, keyName.c_str(), 0, REG_SZ, (LPBYTE)value.c_str(), (DWORD)( (value.length()+1)*2 )); 78 | } 79 | 80 | void Registry::Write(const wstring keyName, const bool value) 81 | { 82 | if (!good) return; 83 | 84 | int val = (value)?1:0; 85 | RegSetValueEx(key, keyName.c_str(), 0, REG_DWORD, (LPBYTE)&val, sizeof(DWORD)); 86 | } 87 | 88 | void Registry::Write(const wstring keyName, const long value) 89 | { 90 | if (!good) return; 91 | RegSetValueEx(key, keyName.c_str(), 0, REG_DWORD, (LPBYTE)&value, sizeof(DWORD)); 92 | } 93 | 94 | void Registry::Write(const wstring keyName, const int value) 95 | { 96 | if (!good) return; 97 | RegSetValueEx(key, keyName.c_str(), 0, REG_DWORD, (LPBYTE)&value, sizeof(DWORD)); 98 | } 99 | 100 | const bool Registry::Read(const wstring keyName, wstring *out, const wstring defaultValue) const 101 | { 102 | // Default the return value immediately 103 | *out = defaultValue; 104 | if (!good) return false; 105 | 106 | // Read the value once to get the size of the string 107 | long result = 0; 108 | DWORD size = 0; 109 | result = RegQueryValueEx(key, keyName.c_str(), 0, NULL, NULL, &size); 110 | 111 | // Read the value again to get the actual string 112 | if (result == ERROR_SUCCESS) 113 | { 114 | wchar_t *data = new wchar_t[size + 1]; 115 | if (!data) return false; 116 | 117 | result = RegQueryValueEx(key, keyName.c_str(), 0, NULL, (LPBYTE)data, &size); 118 | 119 | if (result == ERROR_SUCCESS) *out = wstring(data); 120 | 121 | if (data) delete[] data; 122 | data = 0; 123 | } 124 | 125 | // 'out' would have only been set on success, otherwise the 126 | // default still exists in 'out', so we're all set 127 | return (result == ERROR_SUCCESS); 128 | } 129 | 130 | const bool Registry::Read(const wstring keyName, bool *out, const bool defaultValue) const 131 | { 132 | // Default the return value immediately 133 | *out = defaultValue; 134 | if (!good) return false; 135 | 136 | DWORD data = 0; 137 | DWORD dataSize = sizeof(DWORD); 138 | 139 | const long result = RegQueryValueEx(key, keyName.c_str(), 0, NULL, (LPBYTE)&data, &dataSize); 140 | if (result == ERROR_SUCCESS) *out = !(data == 0); 141 | 142 | return (result == ERROR_SUCCESS); 143 | } 144 | 145 | const bool Registry::Read(const wstring keyName, long *out, const long defaultValue) const 146 | { 147 | // Default the return value immediately 148 | *out = defaultValue; 149 | if (!good) return false; 150 | 151 | DWORD data = 0; 152 | DWORD dataSize = sizeof(DWORD); 153 | 154 | const long result = RegQueryValueEx(key, keyName.c_str(), 0, NULL, (LPBYTE)&data, &dataSize); 155 | if (result == ERROR_SUCCESS) *out = data; 156 | 157 | return (result == ERROR_SUCCESS); 158 | } 159 | 160 | const bool Registry::Read(const wstring keyName, int *out, const int defaultValue) const 161 | { 162 | // Default the return value immediately 163 | *out = defaultValue; 164 | if (!good) return false; 165 | 166 | DWORD data = 0; 167 | DWORD dataSize = sizeof(DWORD); 168 | 169 | const long result = RegQueryValueEx(key, keyName.c_str(), 0, NULL, (LPBYTE)&data, &dataSize); 170 | if (result == ERROR_SUCCESS) *out = (signed)data; 171 | 172 | return (result == ERROR_SUCCESS); 173 | } 174 | 175 | -------------------------------------------------------------------------------- /src/GameState.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #ifndef __GAMESTATE_H 6 | #define __GAMESTATE_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "os.h" 13 | #include "Textures.h" 14 | #include "CompatibleSystem.h" 15 | #include "FrameCounter.h" 16 | 17 | class Renderer; 18 | class Tga; 19 | 20 | class GameStateError : public std::exception 21 | { 22 | public: 23 | GameStateError(const std::string &error) throw() : m_error(error) { } 24 | virtual const char *what() const throw() { return m_error.c_str(); } 25 | 26 | ~GameStateError() throw() { } 27 | 28 | private: 29 | const std::string m_error; 30 | 31 | GameStateError operator=(const GameStateError&); 32 | }; 33 | 34 | class GameStateManager; 35 | 36 | enum GameKey 37 | { 38 | KeySpace = 0x0001, 39 | KeyEscape = 0x0002, 40 | KeyUp = 0x0004, 41 | KeyDown = 0x0008, 42 | KeyLeft = 0x0010, 43 | KeyRight = 0x0020, 44 | KeyEnter = 0x0040, 45 | 46 | KeyF6 = 0x0080, 47 | 48 | KeyPlus = 0x0100, 49 | KeyMinus = 0x0200 50 | }; 51 | 52 | enum MouseButton 53 | { 54 | MouseLeft, 55 | MouseRight 56 | }; 57 | 58 | struct MouseButtons 59 | { 60 | MouseButtons() : left(false), right(false) { } 61 | 62 | bool left; 63 | bool right; 64 | }; 65 | 66 | struct MouseInfo 67 | { 68 | MouseInfo() : x(0), y(0) { } 69 | 70 | int x; 71 | int y; 72 | 73 | MouseButtons held; 74 | MouseButtons newPress; 75 | MouseButtons released; 76 | }; 77 | 78 | class GameState 79 | { 80 | public: 81 | // Don't initialize anything that is dependent 82 | // on the protected functions (GetStateWidth, 83 | // GetStateMilliseconds, etc) here. Wait until 84 | // Init() to do that. 85 | GameState() : m_manager(0), m_state_milliseconds(0), m_last_delta_milliseconds(0) { } 86 | virtual ~GameState() { } 87 | 88 | protected: 89 | 90 | // This is called just after the state's manager 91 | // is set for the first time 92 | virtual void Init() = 0; 93 | 94 | // Called every frame 95 | virtual void Update() = 0; 96 | 97 | // Called each frame. Drawing bounds are [0, 98 | // GetStateWidth()) and [0, GetStateHeight()) 99 | virtual void Draw(Renderer &renderer) const = 0; 100 | 101 | // How long has this state been running 102 | unsigned long GetStateMilliseconds() const { return m_state_milliseconds; } 103 | 104 | // How much time elapsed since the last update 105 | unsigned long GetDeltaMilliseconds() const { return m_last_delta_milliseconds; } 106 | 107 | int GetStateWidth() const; 108 | int GetStateHeight() const; 109 | 110 | // Once finished executing, use this to change 111 | // state to something new. This can only be 112 | // called from inside Update(). After calling 113 | // this function, you're guaranteed that the only 114 | // function that will still be called (before 115 | // the destructor) is Draw(). You *must* be able 116 | // to continue supporting Draw() after you call 117 | // this function. 118 | // 119 | // new_state *must* be dynamically allocated and 120 | // by calling this function you hand off ownership 121 | // of the memory to the state handling subsystem. 122 | void ChangeState(GameState *new_state); 123 | 124 | Tga *GetTexture(Texture tex_name, bool smooth = false) const; 125 | 126 | // These are usable inside Update() 127 | bool IsKeyPressed(GameKey key) const; 128 | const MouseInfo &Mouse() const; 129 | 130 | private: 131 | void SetManager(GameStateManager *manager); 132 | GameStateManager *m_manager; 133 | 134 | void UpdateStateMicroseconds(unsigned long delta_ms) 135 | { 136 | m_state_milliseconds += delta_ms; 137 | m_last_delta_milliseconds = delta_ms; 138 | } 139 | 140 | unsigned long m_state_milliseconds; 141 | unsigned long m_last_delta_milliseconds; 142 | 143 | friend class GameStateManager; 144 | }; 145 | 146 | // Your app calls this from the top level 147 | class GameStateManager 148 | { 149 | public: 150 | GameStateManager(int screen_width, int screen_height) 151 | : m_current_state(0), m_screen_x(screen_width), m_screen_y(screen_height), 152 | m_last_milliseconds(Compatible::GetMilliseconds()), m_next_state(0), m_key_presses(0), m_last_key_presses(0), 153 | m_inside_update(false), m_fps(500.0), m_show_fps(false) 154 | { } 155 | 156 | ~GameStateManager(); 157 | 158 | // first_state must be dynamically allocated. 159 | // GameStateManager takes ownership of the memory 160 | // from this point forward. 161 | void SetInitialState(GameState *first_state); 162 | 163 | void KeyPress(GameKey key); 164 | bool IsKeyPressed(GameKey key) const; 165 | bool IsKeyReleased(GameKey key) const; 166 | 167 | void MousePress(MouseButton button); 168 | void MouseRelease(MouseButton button); 169 | void MouseMove(int x, int y); 170 | const MouseInfo &Mouse() const { return m_mouse; } 171 | 172 | void Update(bool skip_this_update); 173 | void Draw(Renderer &renderer); 174 | 175 | void ChangeState(GameState *new_state); 176 | 177 | Tga *GetTexture(Texture tex_name, bool smooth) const; 178 | 179 | int GetStateWidth() const { return m_screen_x; } 180 | int GetStateHeight() const { return m_screen_y; } 181 | 182 | private: 183 | GameState *m_next_state; 184 | GameState *m_current_state; 185 | 186 | unsigned long m_last_milliseconds; 187 | unsigned long m_key_presses; 188 | unsigned long m_last_key_presses; 189 | 190 | bool m_inside_update; 191 | 192 | MouseInfo m_mouse; 193 | 194 | FrameCounter m_fps; 195 | bool m_show_fps; 196 | 197 | int m_screen_x; 198 | int m_screen_y; 199 | 200 | mutable std::map m_textures; 201 | }; 202 | 203 | 204 | #endif 205 | 206 | -------------------------------------------------------------------------------- /src/libmidi/SynthVolume.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "SynthVolume.h" 6 | 7 | // The following Mixer routines are based off Chen Su's Audio Mixer Functions Demo 8 | // which can be found at: http://www.codeproject.com/audio/admixer.asp 9 | 10 | ReasonableSynthVolume::ReasonableSynthVolume() 11 | { 12 | int mixer_count = mixerGetNumDevs(); 13 | for (int i = 0; i < mixer_count; ++i) 14 | { 15 | m_states.push_back(SynthVolumeState()); 16 | SynthVolumeState &s = m_states[m_states.size() - 1]; 17 | 18 | if (mixerOpen(&s.mixer, 0, 0, 0, MIXER_OBJECTF_MIXER) != MMSYSERR_NOERROR) continue; 19 | 20 | MIXERCAPS caps; 21 | if (mixerGetDevCaps((UINT_PTR)s.mixer, &caps, sizeof(MIXERCAPS)) != MMSYSERR_NOERROR) continue; 22 | 23 | // Open the MIDI "line" of the mixer 24 | MIXERLINE line; 25 | line.cbStruct = sizeof(MIXERLINE); 26 | line.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER; 27 | if (mixerGetLineInfo(reinterpret_cast(s.mixer), &line, MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE) != MMSYSERR_NOERROR) continue; 28 | 29 | 30 | MIXERLINECONTROLS list; 31 | MIXERCONTROL control; 32 | list.cbStruct = sizeof(MIXERLINECONTROLS); 33 | list.dwLineID = line.dwLineID; 34 | list.cControls = 1; 35 | list.cbmxctrl = sizeof(MIXERCONTROL); 36 | list.pamxctrl = &control; 37 | 38 | // Record the "mute" control ID for the MIDI line 39 | list.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE; 40 | if (mixerGetLineControls(reinterpret_cast(s.mixer), &list, MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE) != MMSYSERR_NOERROR) continue; 41 | s.mute_control_id = control.dwControlID; 42 | 43 | // Record the "volume" control ID for the MIDI line (as well as min/max volume settings) 44 | list.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME; 45 | if (mixerGetLineControls(reinterpret_cast(s.mixer), &list, MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE) != MMSYSERR_NOERROR) continue; 46 | s.volume_control_id = control.dwControlID; 47 | DWORD min_volume = control.Bounds.dwMinimum; 48 | DWORD max_volume = control.Bounds.dwMaximum; 49 | 50 | 51 | MIXERCONTROLDETAILS details; 52 | details.cbStruct = sizeof(MIXERCONTROLDETAILS); 53 | details.cChannels = 1; 54 | details.cMultipleItems = 0; 55 | 56 | // Record whether mute is on 57 | MIXERCONTROLDETAILS_BOOLEAN muted_details; 58 | details.dwControlID = s.mute_control_id; 59 | details.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN); 60 | details.paDetails = &muted_details; 61 | if (mixerGetControlDetails(reinterpret_cast(s.mixer), &details, MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_VALUE) != MMSYSERR_NOERROR) continue; 62 | s.old_mute = muted_details.fValue; 63 | 64 | // Always unmute if muted 65 | if (s.old_mute == TRUE) 66 | { 67 | muted_details.fValue = FALSE; 68 | if (mixerSetControlDetails(reinterpret_cast(s.mixer), &details, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE) != MMSYSERR_NOERROR) continue; 69 | } 70 | 71 | // Record current volume 72 | MIXERCONTROLDETAILS_UNSIGNED volume_details; 73 | details.dwControlID = s.volume_control_id; 74 | details.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED); 75 | details.paDetails = &volume_details; 76 | if (mixerGetControlDetails(reinterpret_cast(s.mixer), &details, MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_VALUE) != MMSYSERR_NOERROR) continue; 77 | s.old_volume = volume_details.dwValue; 78 | 79 | // Decide whether we should increase the volume 80 | if (max_volume == min_volume) return; 81 | double volume_percent = (1.0 * s.old_volume - min_volume) / (1.0 * max_volume - min_volume); 82 | 83 | const static double LowVolumePercent = 0.33; 84 | const static double ComfortableVolumePercent = 0.75; 85 | if (volume_percent < LowVolumePercent) 86 | { 87 | DWORD comfortable_volume = static_cast(ComfortableVolumePercent * (max_volume - min_volume)) + min_volume; 88 | 89 | volume_details.dwValue = comfortable_volume; 90 | if (mixerSetControlDetails(reinterpret_cast(s.mixer), &details, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE) != MMSYSERR_NOERROR) continue; 91 | } 92 | } 93 | } 94 | 95 | ReasonableSynthVolume::~ReasonableSynthVolume() 96 | { 97 | int mixer_count = mixerGetNumDevs(); 98 | for (int i = 0; i < mixer_count; ++i) 99 | { 100 | if (m_states.size() <= static_cast(i)) continue; 101 | SynthVolumeState &s = m_states[i]; 102 | 103 | MIXERCONTROLDETAILS details; 104 | details.cbStruct = sizeof(MIXERCONTROLDETAILS); 105 | details.cChannels = 1; 106 | details.cMultipleItems = 0; 107 | 108 | 109 | // Restore Mute 110 | MIXERCONTROLDETAILS_BOOLEAN muted_details; 111 | details.dwControlID = s.mute_control_id; 112 | details.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN); 113 | details.paDetails = &muted_details; 114 | 115 | muted_details.fValue = s.old_mute; 116 | if (mixerSetControlDetails(reinterpret_cast(s.mixer), &details, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE) != MMSYSERR_NOERROR) continue; 117 | 118 | 119 | // Restore Volume 120 | MIXERCONTROLDETAILS_UNSIGNED volume_details; 121 | details.dwControlID = s.volume_control_id; 122 | details.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED); 123 | details.paDetails = &volume_details; 124 | 125 | volume_details.dwValue = s.old_volume; 126 | if (mixerSetControlDetails(reinterpret_cast(s.mixer), &details, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE) != MMSYSERR_NOERROR) continue; 127 | 128 | mixerClose(s.mixer); 129 | } 130 | } 131 | 132 | 133 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | Piano Game teaches you to play piano using any MIDI file. 2 | Visit http://www.synthesiagame.com/ for the latest version of this project. 3 | 4 | -------------------------------------------------------------------------------- 5 | 6 | Piano Game is mostly written by Nicholas Piegdon. 7 | 8 | The following people have also contributed: 9 | - Luis Anton with an early version of the MIDI input code 10 | - Troy Ramos with a suggestion to use Klavarskribo staff notation 11 | - Dave Ray for his STRING/WSTRING macro 12 | - NeHe Productions for their OpenGL base code 13 | - Christian Auby for input octave shifting code 14 | 15 | -------------------------------------------------------------------------------- 16 | 17 | Change Log 18 | 19 | 20 | Release 0.6.1b 21 | -------------- 22 | CHG: Rebranded to remove copyrighted music and trademarked names. 23 | 24 | 25 | Release 0.6.1a 26 | -------------- 27 | FIX: A few bug fixes. 28 | 29 | 30 | Release 0.6.1 31 | ------------- 32 | NEW: On the Mac, output to external devices is now available. 33 | NEW: On the Mac, you can now drag a MIDI file to the application on the Dock or 34 | in Finder or right click a MIDI file and choosing "Open With..." to open. 35 | NEW: In Windows, added a low-tech solution to choose between different monitors 36 | connected to the SAME video card (doesn't work in multi-video-card 37 | situations.) (Set the string key HKCU\Software\PianoGame\Monitor to "0" 38 | for primary monitory, "1" for secondary, etc.) 39 | CHG: Mac OpenGL display now has v-sync enabled. 40 | CHG: Mac game window is now much less obtrusive. Other windows are allowed to 41 | be on top and the Dock and Application bar can both be made visible by 42 | hovering the mouse over there positions for a second or so. The game 43 | window also no longer must be hidden during file selection or error boxes. 44 | FIX: In Windows, MIDI SysEx messages in input are no longer flagged as errors. 45 | FIX: On the Mac, fixed MIDI that used RPN / NRPN controller events (e.g. to 46 | set pitch bend) so they're now handled correctly. 47 | FIX: On the Mac, output devices are now reset much harder than before, so 48 | output device internal state that may be set by one song isn't carried 49 | over to another. 50 | FIX: On the Mac, there was a potential problem with some input devices that 51 | might send bursts of events all at once. Previously, only the first 52 | event in the incoming list of events was recognized. Now, any number of 53 | incoming events will be recognized correctly. 54 | FIX: Picked a font for the Mac version to use that is actually a default 55 | system font. (Apparently "Palatino" only ships with iLife now.) 56 | FIX: On the Mac, fixed a problem where the (modal!) file open dialog could 57 | be hidden underneath the game window, leaving you not choice but to force- 58 | quit the program. 59 | FIX: Disabled OpenGL depth testing to fix a problem where text would sometimes 60 | not appear above other controls (TrackBox, SongBox, etc.) in Windows. 61 | FIX: Score state now displays correctly at low resolutions (like 800x600). 62 | FIX: Title bar no longer pops when changing speed at the beginning of gameplay. 63 | FIX: Text centering and placement on the Mac is now properly implemented. 64 | FIX: MIDI files with non-all-lowercase file extensions can now be selected 65 | in the File Open dialog box. 66 | 67 | 68 | Release 0.6.0 69 | ------------- 70 | NEW: Mac Version! 71 | NEW: All new note and keyboard graphics! 72 | NEW: All new menu graphics. 73 | NEW: Right-click association option in installer. 74 | CHG: Rebranded as Synthesia 75 | CHG: Switched from base 2D OS drawing to 3D accelerated OpenGL drawing. 76 | CHG: Moved status display to the bottom of the screen during play. 77 | CHG: Made default music folder less imposing. Now the game will only default 78 | to the directory the very first run. Afterwards, it will remember the 79 | last file you attempted to play (like it used to). 80 | CHG: Improved exception handling. 81 | FIX: Don't crash if "Synthesia Music" folder doesn't exist. 82 | FIX: No more crash sometimes when app regains focus on the title screen. 83 | 84 | 85 | Release 0.5.1 86 | ------------- 87 | NEW: Your notes are played (in the correct instrument for the track) 88 | NEW: Tooltips on all menu controls and settings. 89 | NEW: 10 Sample songs from Game Music Themes. 90 | NEW: "Stray Notes" metric on stats screen 91 | NEW: "Average Speed" metric on stats screen 92 | NEW: Box around MIDI input test on title screen 93 | NEW: Combo counter and much improved stats display during play. 94 | CHG: Improved Klavarskribo staff notation. 95 | CHG: Match input to closest unplayed note's opportunity window center instead 96 | of matching against the first available unplayed note. 97 | CHG: Swapped blue and yellow in the track color order, making blue the new 98 | "first" color. 99 | CHG: ReasonableSynthVolume object replaces MidiCommOut::SetReasonableSynthVol 100 | and now works for all mixers in the system instead of only the first. 101 | CHG: Darkened the border for a few different colors to increase the contrast 102 | between sharp/flat notes in a series. 103 | CHG: Default to new "Piano Hero Music" folder at startup. (User configurable 104 | in the future.) 105 | FIX: Disabled MIDI input while game is paused to prevent cheating. 106 | FIX: Made MidiCommOut::Reset() actually reset everything by fully closing and 107 | reopening the device. midiOutReset() apparently only turns off the keys 108 | and pedals on each track. It *doesn't* reset patches or volume. 109 | FIX: Reworked MIDI engine to correct a sync/lag issue in songs that started 110 | with a considerable delay before the first note. 111 | FIX: Notes don't draw on top of keys anymore. 112 | FIX: MIDI track default color order is now consistent for all files. 113 | FIX: Two tracks with identical notes would cause the "second" track's note to 114 | not appear. This normally would not matter, as they overlap completely, 115 | but when hiding the first track, the second note would not show. 116 | 117 | 118 | Release 0.5.0 119 | ------------- 120 | NEW: Klavarskribo staff notation (thanks to Troy Ramos for the suggestion!) 121 | NEW: MIDI input instrument selection (early version by Luis Anton) 122 | NEW: MIDI input with note matching and scoring (early version by Luis Anton) 123 | NEW: Note hit/miss effects 124 | NEW: If muted, temporarily unmute the selected MIDI device during gameplay 125 | NEW: Level progress bar along top of game play screen 126 | NEW: In-game open file dialog 127 | NEW: Song finish screen with score, rating, and statistics 128 | 129 | 130 | Release 0.4.0 131 | ------------- 132 | NEW: Initial Release 133 | 134 | -------------------------------------------------------------------------------- /src/GameState.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "GameState.h" 6 | #include "Renderer.h" 7 | #include "Textures.h" 8 | #include "CompatibleSystem.h" 9 | #include "Tga.h" 10 | #include "os_graphics.h" 11 | 12 | // For FPS display 13 | #include "TextWriter.h" 14 | #include 15 | 16 | Tga *GameState::GetTexture(Texture tex_name, bool smooth) const 17 | { 18 | if (!m_manager) throw GameStateError("Cannot retrieve texture if manager not set!"); 19 | return m_manager->GetTexture(tex_name, smooth); 20 | } 21 | 22 | void GameState::ChangeState(GameState *new_state) 23 | { 24 | if (!m_manager) throw GameStateError("Cannot change state if manager not set!"); 25 | m_manager->ChangeState(new_state); 26 | } 27 | 28 | int GameState::GetStateWidth() const 29 | { 30 | if (!m_manager) throw GameStateError("Cannot retrieve state width if manager not set!"); 31 | return m_manager->GetStateWidth(); 32 | } 33 | 34 | int GameState::GetStateHeight() const 35 | { 36 | if (!m_manager) throw GameStateError("Cannot retrieve state height if manager not set!"); 37 | return m_manager->GetStateHeight(); 38 | } 39 | 40 | bool GameState::IsKeyPressed(GameKey key) const 41 | { 42 | if (!m_manager) throw GameStateError("Cannot determine key presses if manager not set!"); 43 | return m_manager->IsKeyPressed(key); 44 | } 45 | 46 | const MouseInfo &GameState::Mouse() const 47 | { 48 | if (!m_manager) throw GameStateError("Cannot determine mouse input if manager not set!"); 49 | return m_manager->Mouse(); 50 | } 51 | 52 | void GameState::SetManager(GameStateManager *manager) 53 | { 54 | if (m_manager) throw GameStateError("State already has a manager!"); 55 | 56 | m_manager = manager; 57 | Init(); 58 | } 59 | 60 | 61 | 62 | 63 | GameStateManager::~GameStateManager() 64 | { 65 | for (std::map::iterator i = m_textures.begin(); i != m_textures.end(); ++i) 66 | { 67 | if (i->second) Tga::Release(i->second); 68 | i->second = 0; 69 | } 70 | } 71 | 72 | Tga *GameStateManager::GetTexture(Texture tex_name, bool smooth) const 73 | { 74 | if (!m_textures[tex_name]) 75 | { 76 | m_textures[tex_name] = Tga::Load(TextureResourceNames[tex_name]); 77 | } 78 | 79 | m_textures[tex_name]->SetSmooth(smooth); 80 | 81 | return m_textures[tex_name]; 82 | } 83 | 84 | void GameStateManager::KeyPress(GameKey key) 85 | { 86 | m_key_presses |= static_cast(key); 87 | } 88 | 89 | bool GameStateManager::IsKeyPressed(GameKey key) const 90 | { 91 | return ( (m_key_presses & static_cast(key)) != 0); 92 | } 93 | 94 | bool GameStateManager::IsKeyReleased(GameKey key) const 95 | { 96 | return (!IsKeyPressed(key) && ((m_last_key_presses & static_cast(key)) != 0)); 97 | } 98 | 99 | void GameStateManager::MousePress(MouseButton button) 100 | { 101 | switch (button) 102 | { 103 | case MouseLeft: 104 | m_mouse.held.left = true; 105 | m_mouse.released.left = false; 106 | m_mouse.newPress.left = true; 107 | break; 108 | 109 | case MouseRight: 110 | m_mouse.held.right = true; 111 | m_mouse.released.right = false; 112 | m_mouse.newPress.right = true; 113 | break; 114 | } 115 | } 116 | 117 | void GameStateManager::MouseRelease(MouseButton button) 118 | { 119 | switch (button) 120 | { 121 | case MouseLeft: 122 | m_mouse.held.left = false; 123 | m_mouse.released.left = true; 124 | m_mouse.newPress.left = false; 125 | break; 126 | 127 | case MouseRight: 128 | m_mouse.held.right = false; 129 | m_mouse.released.right = true; 130 | m_mouse.newPress.right = false; 131 | break; 132 | } 133 | } 134 | 135 | void GameStateManager::MouseMove(int x, int y) 136 | { 137 | m_mouse.x = x; 138 | m_mouse.y = y; 139 | } 140 | 141 | void GameStateManager::SetInitialState(GameState *first_state) 142 | { 143 | if (m_current_state) throw GameStateError("Cannot set an initial state because GameStateManager already has a state!"); 144 | 145 | first_state->SetManager(this); 146 | m_current_state = first_state; 147 | } 148 | 149 | void GameStateManager::ChangeState(GameState *new_state) 150 | { 151 | if (!m_current_state) throw GameStateError("Cannot change state without a state! Use SetInitialState()!"); 152 | if (!new_state) throw GameStateError("Cannot change to a null state!"); 153 | 154 | if (!m_inside_update) throw GameStateError("ChangeState must be called from inside another state's " 155 | "Update() function! (This is so we can guarantee the ordering of the draw/update calls.)"); 156 | 157 | m_next_state = new_state; 158 | } 159 | 160 | 161 | 162 | void GameStateManager::Update(bool skip_this_update) 163 | { 164 | // Manager's timer grows constantly 165 | const unsigned long now = Compatible::GetMilliseconds(); 166 | const unsigned long delta = now - m_last_milliseconds; 167 | m_last_milliseconds = now; 168 | 169 | // Now that we've updated the time, we can return if 170 | // we've been told to skip this one. 171 | if (skip_this_update) return; 172 | 173 | m_fps.Frame(delta); 174 | if (IsKeyReleased(KeyF6)) m_show_fps = !m_show_fps; 175 | 176 | if (m_next_state && m_current_state) 177 | { 178 | delete m_current_state; 179 | m_current_state = 0; 180 | 181 | // We return here to insert a blank frame (that may or may 182 | // not last a long time) while the next state's Init() 183 | // and first Update() are being called. 184 | return; 185 | } 186 | 187 | if (m_next_state) 188 | { 189 | m_current_state = m_next_state; 190 | m_next_state = 0; 191 | 192 | m_current_state->SetManager(this); 193 | } 194 | 195 | if (!m_current_state) return; 196 | 197 | m_inside_update = true; 198 | 199 | m_current_state->m_last_delta_milliseconds = delta; 200 | m_current_state->m_state_milliseconds += delta; 201 | m_current_state->Update(); 202 | 203 | m_inside_update = false; 204 | 205 | // Reset our keypresses for the next frame 206 | m_last_key_presses = m_key_presses; 207 | m_key_presses = 0; 208 | 209 | // Reset our mouse clicks for the next frame 210 | m_mouse.newPress = MouseButtons(); 211 | m_mouse.released = MouseButtons(); 212 | } 213 | 214 | void GameStateManager::Draw(Renderer &renderer) 215 | { 216 | if (!m_current_state) return; 217 | 218 | // NOTE: Sweet transition effects are *very* possible here... rendering 219 | // the previous state *and* the current state during some transition 220 | // would be really easy. 221 | 222 | const static float gray = 64.0f / 255.0f; 223 | glClearColor(gray, gray, gray, 1.0f); 224 | 225 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 226 | 227 | glMatrixMode(GL_MODELVIEW); 228 | glLoadIdentity(); 229 | glTranslatef(0., static_cast(GetStateHeight()), 0.); 230 | glScalef (1., -1., 1.); 231 | glTranslatef(0.375, 0.375, 0.); 232 | 233 | m_current_state->Draw(renderer); 234 | 235 | if (m_show_fps) 236 | { 237 | TextWriter fps_writer(0, 0, renderer); 238 | fps_writer << Text(WSTRING(L"FPS: "), Gray) << Text(WSTRING(std::setprecision(6) << m_fps.GetFramesPerSecond()), White); 239 | } 240 | 241 | glFlush (); 242 | renderer.SwapBuffers(); 243 | } 244 | -------------------------------------------------------------------------------- /src/Tga.cpp: -------------------------------------------------------------------------------- 1 | #include "Tga.h" 2 | 3 | #include "os.h" 4 | #include "os_graphics.h" 5 | #include "string_util.h" 6 | #include "PianoGameError.h" 7 | 8 | Tga* Tga::Load(const std::wstring &resource_name) 9 | { 10 | 11 | #ifdef WIN32 12 | // This is for future use. For now, we're limiting 13 | // ourselves to the current executable only. 14 | const HMODULE module = 0; 15 | 16 | HRSRC resource_id = FindResource(module, resource_name.c_str(), L"GRAPHICS"); 17 | if (!resource_id) throw PianoGameError(L"Couldn't find TGA resource."); 18 | 19 | HGLOBAL resource = LoadResource(module, resource_id); 20 | if (!resource) throw PianoGameError(L"Couldn't load TGA resource."); 21 | 22 | const unsigned char *bytes = reinterpret_cast(LockResource(resource)); 23 | if (!bytes) throw PianoGameError(L"Couldn't lock TGA resource."); 24 | 25 | Tga *ret = LoadFromData(bytes); 26 | FreeResource(resource); 27 | 28 | #else 29 | 30 | // Append extension on the Mac 31 | std::wstring full_name = WSTRING(resource_name << L".tga"); 32 | 33 | CFURLRef url = CFBundleCopyResourceURL(CFBundleGetMainBundle(), MacStringFromWide(full_name).get(), 0, 0); 34 | if (!url) throw PianoGameError(L"Couldn't find TGA resource."); 35 | 36 | OSStatus status; 37 | CFDataRef data; 38 | Boolean success = CFURLCreateDataAndPropertiesFromResource(0, url, &data, 0, 0, &status); 39 | if (!success || status != 0) throw PianoGameError(L"Couldn't load TGA resource."); 40 | 41 | const UInt8 *bytes = CFDataGetBytePtr(data); 42 | 43 | Tga *ret = LoadFromData(bytes); 44 | CFRelease(data); 45 | 46 | #endif 47 | 48 | ret->SetSmooth(false); 49 | 50 | return ret; 51 | } 52 | 53 | void Tga::Release(Tga *tga) 54 | { 55 | if (!tga) return; 56 | 57 | glDeleteTextures(1, &tga->m_texture_id); 58 | 59 | delete tga; 60 | } 61 | 62 | const static int TgaTypeHeaderLength = 12; 63 | const static unsigned char UncompressedTgaHeader[TgaTypeHeaderLength] = {0,0,2,0,0,0,0,0,0,0,0,0}; 64 | const static unsigned char CompressedTgaHeader[TgaTypeHeaderLength] = {0,0,10,0,0,0,0,0,0,0,0,0}; 65 | 66 | void Tga::SetSmooth(bool smooth) 67 | { 68 | GLint filter = GL_NEAREST; 69 | if (smooth) filter = GL_LINEAR; 70 | 71 | glBindTexture(GL_TEXTURE_2D, m_texture_id); 72 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); 73 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); 74 | } 75 | 76 | enum TgaType 77 | { 78 | TgaUncompressed, 79 | TgaCompressed, 80 | TgaUnknown 81 | }; 82 | 83 | Tga *Tga::LoadFromData(const unsigned char *bytes) 84 | { 85 | if (!bytes) return 0; 86 | 87 | const unsigned char *pos = bytes; 88 | 89 | TgaType type = TgaUnknown; 90 | if (memcmp(UncompressedTgaHeader, pos, TgaTypeHeaderLength) == 0) type = TgaUncompressed; 91 | if (memcmp(CompressedTgaHeader, pos, TgaTypeHeaderLength) == 0) type = TgaCompressed; 92 | 93 | if (type == TgaUnknown) 94 | { 95 | throw PianoGameError(L"Unsupported TGA type."); 96 | } 97 | 98 | // We're done with the type header 99 | pos += TgaTypeHeaderLength; 100 | 101 | unsigned int width = pos[1] * 256 + pos[0]; 102 | unsigned int height = pos[3] * 256 + pos[2]; 103 | unsigned int bpp = pos[4]; 104 | 105 | // We're done with the data header 106 | const static int TgaDataHeaderLength = 6; 107 | pos += TgaDataHeaderLength; 108 | 109 | if (width <= 0 || height <= 0) 110 | { 111 | throw PianoGameError(L"Invalid TGA dimensions."); 112 | } 113 | 114 | if (bpp != 24 && bpp != 32) 115 | { 116 | throw PianoGameError(L"Unsupported TGA BPP."); 117 | } 118 | 119 | const unsigned int data_size = width * height * bpp/8; 120 | unsigned char *image_data = new unsigned char[data_size]; 121 | 122 | Tga *t = 0; 123 | if (type == TgaCompressed) t = LoadCompressed(pos, image_data, width, height, bpp); 124 | if (type == TgaUncompressed) t = LoadUncompressed(pos, image_data, data_size, width, height, bpp); 125 | 126 | delete[] image_data; 127 | return t; 128 | } 129 | 130 | Tga *Tga::LoadUncompressed(const unsigned char *src, unsigned char *dest, unsigned int size, unsigned int width, unsigned int height, unsigned int bpp) 131 | { 132 | // We can use most of the data as-is with little modification 133 | memcpy(dest, src, size); 134 | 135 | for (unsigned int cswap = 0; cswap < size; cswap += bpp/8) 136 | { 137 | dest[cswap] ^= dest[cswap+2] ^= dest[cswap] ^= dest[cswap+2]; 138 | } 139 | 140 | return BuildFromParameters(dest, width, height, bpp); 141 | } 142 | 143 | Tga *Tga::LoadCompressed(const unsigned char *src, unsigned char *dest, unsigned int width, unsigned int height, unsigned int bpp) 144 | { 145 | const unsigned char *pos = src; 146 | 147 | const unsigned int BytesPerPixel = bpp / 8; 148 | const unsigned int PixelCount = height * width; 149 | 150 | const static unsigned int MaxBytesPerPixel = 4; 151 | unsigned char pixel_buffer[MaxBytesPerPixel]; 152 | 153 | unsigned int pixel = 0; 154 | unsigned int byte = 0; 155 | 156 | while (pixel < PixelCount) 157 | { 158 | unsigned char chunkheader = 0; 159 | memcpy(&chunkheader, pos, sizeof(unsigned char)); 160 | pos += sizeof(unsigned char); 161 | 162 | if (chunkheader < 128) 163 | { 164 | chunkheader++; 165 | for (short i = 0; i < chunkheader; i++) 166 | { 167 | memcpy(pixel_buffer, pos, BytesPerPixel); 168 | pos += BytesPerPixel; 169 | 170 | dest[byte + 0] = pixel_buffer[2]; 171 | dest[byte + 1] = pixel_buffer[1]; 172 | dest[byte + 2] = pixel_buffer[0]; 173 | if (BytesPerPixel == 4) dest[byte + 3] = pixel_buffer[3]; 174 | 175 | byte += BytesPerPixel; 176 | pixel++; 177 | 178 | if (pixel > PixelCount) throw PianoGameError(L"Too many pixels in TGA."); 179 | } 180 | } 181 | else 182 | { 183 | chunkheader -= 127; 184 | 185 | memcpy(pixel_buffer, pos, BytesPerPixel); 186 | pos += BytesPerPixel; 187 | 188 | for (short i = 0; i < chunkheader; i++) 189 | { 190 | dest[byte + 0] = pixel_buffer[2]; 191 | dest[byte + 1] = pixel_buffer[1]; 192 | dest[byte + 2] = pixel_buffer[0]; 193 | if (BytesPerPixel == 4) dest[byte + 3] = pixel_buffer[3]; 194 | 195 | byte += BytesPerPixel; 196 | pixel++; 197 | 198 | if (pixel > PixelCount) throw PianoGameError(L"Too many pixels in TGA."); 199 | } 200 | } 201 | } 202 | 203 | return BuildFromParameters(dest, width, height, bpp); 204 | } 205 | 206 | 207 | Tga *Tga::BuildFromParameters(const unsigned char *raw, unsigned int width, unsigned int height, unsigned int bpp) 208 | { 209 | unsigned int pixel_format = 0; 210 | if (bpp == 24) pixel_format = GL_RGB; 211 | if (bpp == 32) pixel_format = GL_RGBA; 212 | 213 | TextureId id; 214 | glGenTextures(1, &id); 215 | if (!id) return 0; 216 | 217 | glBindTexture(GL_TEXTURE_2D, id); 218 | glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 219 | glTexImage2D(GL_TEXTURE_2D, 0, bpp/8, width, height, 0, pixel_format, GL_UNSIGNED_BYTE, raw); 220 | 221 | Tga *t = new Tga(); 222 | t->m_width = width; 223 | t->m_height = height; 224 | t->m_texture_id = id; 225 | 226 | return t; 227 | } 228 | -------------------------------------------------------------------------------- /src/libmidi/MidiTrack.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "MidiTrack.h" 6 | #include "MidiEvent.h" 7 | #include "MidiUtil.h" 8 | #include "Midi.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | 16 | MidiTrack MidiTrack::ReadFromStream(std::istream &stream) 17 | { 18 | // Verify the track header 19 | const static string MidiTrackHeader = "MTrk"; 20 | 21 | // I could use (MidiTrackHeader.length() + 1), but then this has to be 22 | // dynamically allocated. More hassle than it's worth. MIDI is well 23 | // defined and will always have a 4-byte header. We use 5 so we get 24 | // free null termination. 25 | char header_id[5] = { 0, 0, 0, 0, 0 }; 26 | unsigned long track_length; 27 | 28 | stream.read(header_id, static_cast(MidiTrackHeader.length())); 29 | stream.read(reinterpret_cast(&track_length), sizeof(unsigned long)); 30 | 31 | if (stream.fail()) throw MidiError(MidiError_TrackHeaderTooShort); 32 | 33 | string header(header_id); 34 | if (header != MidiTrackHeader) throw MidiError_BadTrackHeaderType; 35 | 36 | // Pull the full track out of the file all at once -- there is an 37 | // End-Of-Track event, but this allows us handle malformed MIDI a 38 | // little more gracefully. 39 | track_length = BigToSystem32(track_length); 40 | char *buffer = new char[track_length + 1]; 41 | buffer[track_length] = 0; 42 | 43 | stream.read(buffer, track_length); 44 | if (stream.fail()) 45 | { 46 | delete[] buffer; 47 | throw MidiError(MidiError_TrackTooShort); 48 | } 49 | 50 | // We have to jump through a couple hoops because istringstream 51 | // can't handle binary data unless constructed through an std::string. 52 | string buffer_string(buffer, track_length); 53 | istringstream event_stream(buffer_string, ios::binary); 54 | delete[] buffer; 55 | 56 | MidiTrack t; 57 | 58 | // Read events until we run out of track 59 | char last_status = 0; 60 | unsigned long current_pulse_count = 0; 61 | while (event_stream.peek() != char_traits::eof()) 62 | { 63 | MidiEvent ev = MidiEvent::ReadFromStream(event_stream, last_status); 64 | last_status = ev.StatusCode(); 65 | 66 | t.m_events.push_back(ev); 67 | 68 | current_pulse_count += ev.GetDeltaPulses(); 69 | t.m_event_pulses.push_back(current_pulse_count); 70 | } 71 | 72 | t.BuildNoteSet(); 73 | t.DiscoverInstrument(); 74 | 75 | return t; 76 | } 77 | 78 | struct NoteInfo 79 | { 80 | int velocity; 81 | unsigned char channel; 82 | unsigned long pulses; 83 | }; 84 | 85 | void MidiTrack::BuildNoteSet() 86 | { 87 | m_note_set.clear(); 88 | 89 | // Keep a list of all the notes currently "on" (and the pulse that 90 | // it was started). On a note_on event, we create an element. On 91 | // a note_off event we check that an element exists, make a "Note", 92 | // and remove the element from the list. If there is already an 93 | // element on a note_on we both cap off the previous "Note" and 94 | // begin a new one. 95 | // 96 | // A note_on with velocity 0 is a note_off 97 | std::map m_active_notes; 98 | 99 | for (size_t i = 0; i < m_events.size(); ++i) 100 | { 101 | const MidiEvent &ev = m_events[i]; 102 | if (ev.Type() != MidiEventType_NoteOn && ev.Type() != MidiEventType_NoteOff) continue; 103 | 104 | bool on = (ev.Type() == MidiEventType_NoteOn && ev.NoteVelocity() > 0); 105 | NoteId id = ev.NoteNumber(); 106 | 107 | // Check for an active note 108 | std::map::iterator find_ret = m_active_notes.find(id); 109 | bool active_event = (find_ret != m_active_notes.end()); 110 | 111 | // Close off the last event if there was one 112 | if (active_event) 113 | { 114 | Note n; 115 | n.start = find_ret->second.pulses; 116 | n.end = m_event_pulses[i]; 117 | n.note_id = id; 118 | n.channel = find_ret->second.channel; 119 | n.velocity = find_ret->second.velocity; 120 | 121 | // NOTE: This must be set at the next level up. The track 122 | // itself has no idea what its index is. 123 | n.track_id = 0; 124 | 125 | // Add a note and remove this NoteId from the active list 126 | m_note_set.insert(n); 127 | m_active_notes.erase(find_ret); 128 | } 129 | 130 | // We've handled any active events. If this was a note_off we're done. 131 | if (!on) continue; 132 | 133 | // Add a new active event 134 | NoteInfo info; 135 | info.channel = ev.Channel(); 136 | info.velocity = ev.NoteVelocity(); 137 | info.pulses = m_event_pulses[i]; 138 | 139 | m_active_notes[id] = info; 140 | } 141 | 142 | if (m_active_notes.size() > 0) 143 | { 144 | // LOGTODO! 145 | 146 | // This is mostly non-critical. 147 | // 148 | // Erroring out would be needlessly restrictive against 149 | // promiscuous MIDI files. As-is, a note just won't be 150 | // inserted if it isn't closed properly. 151 | } 152 | } 153 | 154 | void MidiTrack::DiscoverInstrument() 155 | { 156 | // Default to Program 0 per the MIDI Standard 157 | m_instrument_id = 0; 158 | bool instrument_found = false; 159 | 160 | // These are actually 10 and 16 in the MIDI standard. However, MIDI 161 | // channels are 1-based facing the user. They're stored 0-based. 162 | const static int PercussionChannel1 = 9; 163 | const static int PercussionChannel2 = 15; 164 | 165 | // Check to see if any/all of the notes 166 | // in this track use Channel 10. 167 | bool any_note_uses_percussion = false; 168 | bool any_note_does_not_use_percussion = false; 169 | 170 | for (size_t i = 0; i < m_events.size(); ++i) 171 | { 172 | const MidiEvent &ev = m_events[i]; 173 | if (ev.Type() != MidiEventType_NoteOn) continue; 174 | 175 | if (ev.Channel() == PercussionChannel1 || ev.Channel() == PercussionChannel2) any_note_uses_percussion = true; 176 | if (ev.Channel() != PercussionChannel1 && ev.Channel() != PercussionChannel2) any_note_does_not_use_percussion = true; 177 | } 178 | 179 | if (any_note_uses_percussion && !any_note_does_not_use_percussion) 180 | { 181 | m_instrument_id = InstrumentIdPercussion; 182 | return; 183 | } 184 | 185 | if (any_note_uses_percussion && any_note_does_not_use_percussion) 186 | { 187 | m_instrument_id = InstrumentIdVarious; 188 | return; 189 | } 190 | 191 | for (size_t i = 0; i < m_events.size(); ++i) 192 | { 193 | const MidiEvent &ev = m_events[i]; 194 | if (ev.Type() != MidiEventType_ProgramChange) continue; 195 | 196 | // If we've already hit a different instrument in this 197 | // same track, just tag it as "various" and exit early 198 | // 199 | // Also check that the same instrument isn't just set 200 | // multiple times in the same track 201 | if (instrument_found && m_instrument_id != ev.ProgramNumber()) 202 | { 203 | m_instrument_id = InstrumentIdVarious; 204 | return; 205 | } 206 | 207 | m_instrument_id = ev.ProgramNumber(); 208 | instrument_found = true; 209 | } 210 | } 211 | 212 | void MidiTrack::SetTrackId(size_t track_id) 213 | { 214 | NoteSet old = m_note_set; 215 | 216 | m_note_set.clear(); 217 | for (NoteSet::const_iterator i = old.begin(); i != old.end(); ++i) 218 | { 219 | Note n = *i; 220 | n.track_id = track_id; 221 | 222 | m_note_set.insert(n); 223 | } 224 | } 225 | 226 | void MidiTrack::Reset() 227 | { 228 | m_running_microseconds = 0; 229 | m_last_event = -1; 230 | 231 | m_notes_remaining = static_cast(m_note_set.size()); 232 | } 233 | 234 | MidiEventList MidiTrack::Update(microseconds_t delta_microseconds) 235 | { 236 | m_running_microseconds += delta_microseconds; 237 | 238 | MidiEventList evs; 239 | for (size_t i = m_last_event + 1; i < m_events.size(); ++i) 240 | { 241 | if (m_event_usecs[i] <= m_running_microseconds) 242 | { 243 | evs.push_back(m_events[i]); 244 | m_last_event = static_cast(i); 245 | 246 | if (m_events[i].Type() == MidiEventType_NoteOn && 247 | m_events[i].NoteVelocity() > 0) m_notes_remaining--; 248 | } 249 | else break; 250 | } 251 | 252 | return evs; 253 | } 254 | -------------------------------------------------------------------------------- /src/file_selector.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "PianoGameError.h" 6 | #include "file_selector.h" 7 | #include "UserSettings.h" 8 | #include "string_util.h" 9 | #include "os.h" 10 | 11 | #include 12 | using namespace std; 13 | 14 | #ifdef WIN32 15 | #include 16 | const static wchar_t PathDelimiter = L'\\'; 17 | #else 18 | #include "ApplicationServices/ApplicationServices.h" 19 | const static wchar_t PathDelimiter = L'/'; 20 | #endif 21 | 22 | namespace FileSelector 23 | { 24 | 25 | #ifndef WIN32 26 | 27 | static pascal Boolean NavOpenFilterProc(AEDesc *item, void *info, NavCallBackUserData callBackUD, NavFilterModes filterMode) 28 | { 29 | OSStatus status; 30 | Boolean outCanOpenAsMovie; 31 | Boolean canViewItem = false; 32 | 33 | if (!item->descriptorType == typeFSRef) return false; 34 | if (((NavFileOrFolderInfo*)info)->isFolder == true) return true; 35 | 36 | FSRef fsRef; 37 | status = AEGetDescData(item, &fsRef, sizeof(fsRef)); 38 | if (status != noErr) throw PianoGameError(WSTRING(L"Couldn't get item description. Error code: " << static_cast(status))); 39 | 40 | const static int BufferSize(1024); 41 | char path_buffer[BufferSize]; 42 | status = FSRefMakePath(&fsRef, (UInt8*)path_buffer, BufferSize); 43 | if (status != noErr) throw PianoGameError(WSTRING(L"Couldn't get file path. Error code: " << static_cast(status))); 44 | 45 | std::string path(path_buffer); 46 | if (path.length() < 5) return false; 47 | 48 | std::string path_lower(StringLower(path)); 49 | 50 | bool allowed = false; 51 | const static std::string allowed1(".mid"); 52 | const static std::string allowed2(".midi"); 53 | allowed = allowed || (path_lower.substr(path_lower.length() - allowed1.length()) == allowed1); 54 | allowed = allowed || (path_lower.substr(path_lower.length() - allowed2.length()) == allowed2); 55 | 56 | return allowed; 57 | } 58 | 59 | #endif 60 | 61 | 62 | void RequestMidiFilename(std::wstring *returned_filename, std::wstring *returned_file_title) 63 | { 64 | // Grab the filename of the last song we played 65 | // and pre-load it into the open dialog 66 | wstring last_filename = UserSetting::Get(L"Last File", L""); 67 | 68 | const static int BufferSize = 512; 69 | wchar_t filename[BufferSize] = L""; 70 | wchar_t filetitle[BufferSize] = L""; 71 | 72 | #ifdef WIN32 73 | // Try to populate our "File Open" box with the last file selected 74 | if (StringCbCopyW(filename, BufferSize, last_filename.c_str()) == STRSAFE_E_INSUFFICIENT_BUFFER) 75 | { 76 | // If there wasn't a last file, default to the built-in Music directory 77 | filename[0] = L'\0'; 78 | } 79 | 80 | wstring default_dir; 81 | bool default_directory = false; 82 | if (last_filename.length() == 0) 83 | { 84 | default_directory = true; 85 | default_dir = UserSetting::Get(L"Default Music Directory", L""); 86 | 87 | if (!SetCurrentDirectory(default_dir.c_str())) 88 | { 89 | // LOGTODO! 90 | // This is non-critical. No action required. 91 | } 92 | } 93 | 94 | OPENFILENAME ofn; 95 | ZeroMemory(&ofn, sizeof(OPENFILENAME)); 96 | ofn.lStructSize = sizeof(OPENFILENAME); 97 | ofn.hwndOwner = 0; 98 | ofn.lpstrTitle = L"Piano Game: Choose a MIDI song to play"; 99 | ofn.lpstrFilter = L"MIDI Files (*.mid)\0*.mid;*.midi\0All Files (*.*)\0*.*\0"; 100 | ofn.lpstrFile = filename; 101 | ofn.nMaxFile = BufferSize; 102 | ofn.lpstrFileTitle = filetitle; 103 | ofn.lpstrInitialDir = default_dir.c_str(); 104 | ofn.nMaxFileTitle = BufferSize; 105 | ofn.lpstrDefExt = L"mid"; 106 | ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; 107 | 108 | if (GetOpenFileName(&ofn)) 109 | { 110 | std::wstring filename = WSTRING(ofn.lpstrFile); 111 | 112 | SetLastMidiFilename(filename); 113 | 114 | if (returned_file_title) *returned_file_title = WSTRING(filetitle); 115 | if (returned_filename) *returned_filename = filename; 116 | return; 117 | } 118 | 119 | if (returned_file_title) *returned_file_title = L""; 120 | if (returned_filename) *returned_filename = L""; 121 | 122 | #else 123 | 124 | OSStatus status; 125 | 126 | NavDialogCreationOptions options; 127 | status = NavGetDefaultDialogCreationOptions(&options); 128 | if (status != noErr) throw PianoGameError(WSTRING(L"Couldn't create dialog options. Error code: " << static_cast(status))); 129 | 130 | options.windowTitle = CFSTR("Piano Game: Choose a MIDI song to play"); 131 | 132 | // TODO: Should clean this up at shut-down 133 | static NavObjectFilterUPP navFilterUPP(0); 134 | if (navFilterUPP == 0) navFilterUPP = NewNavObjectFilterUPP(NavOpenFilterProc); 135 | 136 | NavDialogRef navDialog(0); 137 | status = NavCreateChooseFileDialog(&options, 0, 0, 0, navFilterUPP, 0, &navDialog); 138 | if (status != noErr) throw PianoGameError(WSTRING(L"Couldn't create open dialog. Error code: " << static_cast(status))); 139 | 140 | status = NavDialogRun(navDialog); 141 | if (status != noErr) throw PianoGameError(WSTRING(L"Couldn't run open dialog. Error code: " << static_cast(status))); 142 | 143 | NavReplyRecord navReply; 144 | status = NavDialogGetReply(navDialog, &navReply); 145 | 146 | if (status == userCanceledErr || !navReply.validRecord) 147 | { 148 | NavDisposeReply(&navReply); 149 | 150 | if (returned_file_title) *returned_file_title = L""; 151 | if (returned_filename) *returned_filename = L""; 152 | return; 153 | } 154 | 155 | long item_count = 0; 156 | status = AECountItems(&navReply.selection, &item_count); 157 | if (status != noErr) throw PianoGameError(WSTRING(L"Couldn't count resulting items from open dialog. Error code: " << static_cast(status))); 158 | 159 | for (long i = 1; i <= item_count; i++) 160 | { 161 | FSRef fsRef; 162 | status = AEGetNthPtr(&navReply.selection, i, typeFSRef, 0, 0, &fsRef, sizeof(FSRef), 0); 163 | if (status != noErr) throw PianoGameError(WSTRING(L"Couldn't get FSRef pointer from open dialog. Error code: " << static_cast(status))); 164 | 165 | CFStringRef file_title; 166 | status = LSCopyDisplayNameForRef( &fsRef, &file_title ); 167 | if (status != noErr) throw PianoGameError(WSTRING(L"Couldn't get file title. Error code: " << static_cast(status))); 168 | 169 | const static int BufferSize(1024); 170 | char path_buffer[BufferSize]; 171 | status = FSRefMakePath(&fsRef, (UInt8*)path_buffer, BufferSize); 172 | if (status != noErr) throw PianoGameError(WSTRING(L"Couldn't get file path. Error code: " << static_cast(status))); 173 | 174 | std::string narrow_path(path_buffer); 175 | std::wstring filepath(narrow_path.begin(), narrow_path.end()); 176 | 177 | if (returned_file_title) *returned_file_title = WideFromMacString(file_title); 178 | if (returned_filename) *returned_filename = filepath; 179 | 180 | CFRelease(file_title); 181 | } 182 | 183 | NavDisposeReply(&navReply); 184 | 185 | #endif 186 | } 187 | 188 | void SetLastMidiFilename(const std::wstring &filename) 189 | { 190 | UserSetting::Set(L"Last File", filename); 191 | } 192 | 193 | std::wstring TrimFilename(const std::wstring &filename) 194 | { 195 | wstring song_title = filename; 196 | wstring song_lower = StringLower(song_title); 197 | 198 | // Strip off known file extensions 199 | set extensions; 200 | extensions.insert(L".mid"); 201 | extensions.insert(L".midi"); 202 | for (set::const_iterator i = extensions.begin(); i != extensions.end(); ++i) 203 | { 204 | wstring extension = StringLower(*i); 205 | wstring::size_type len = extension.length(); 206 | 207 | wstring song_end = song_lower.substr(std::max((unsigned long)0, (unsigned long)(song_lower.length() - len)), song_lower.length()); 208 | if (song_end == extension) song_title = song_title.substr(0, song_title.length() - len); 209 | song_lower = StringLower(song_title); 210 | } 211 | 212 | // Strip off path 213 | for (wstring::size_type i = song_title.length(); i != 0; --i) 214 | { 215 | if (song_title[i-1] == PathDelimiter) 216 | { 217 | song_title = song_title.substr(i, song_title.length()); 218 | break; 219 | } 220 | } 221 | 222 | return song_title; 223 | } 224 | 225 | }; // End namespace 226 | -------------------------------------------------------------------------------- /src/TextWriter.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include 6 | 7 | #include "TextWriter.h" 8 | #include "Renderer.h" 9 | #include "PianoGameError.h" 10 | #include "os_graphics.h" 11 | 12 | #ifdef WIN32 13 | // TODO: This should be deleted at shutdown 14 | static std::map font_handle_lookup; 15 | static int next_call_list_start = 1; 16 | #else 17 | // TODO: This should be deleted at shutdown 18 | static std::map atsu_style_lookup; 19 | #endif 20 | 21 | // TODO: This should be deleted at shutdown 22 | static std::map font_size_lookup; 23 | 24 | TextWriter::TextWriter(int in_x, int in_y, Renderer &in_renderer, bool in_centered, int in_size, std::wstring fontname) : 25 | x(in_x), y(in_y), size(in_size), original_x(0), last_line_height(0), centered(in_centered), renderer(in_renderer) 26 | { 27 | x += renderer.m_xoffset; 28 | original_x = x; 29 | 30 | y += renderer.m_yoffset; 31 | 32 | #ifdef WIN32 33 | 34 | Context c = renderer.m_context; 35 | point_size = MulDiv(size, GetDeviceCaps(c, LOGPIXELSY), 72); 36 | 37 | HFONT font = 0; 38 | if (font_size_lookup[in_size] == 0) 39 | { 40 | // Set up the LOGFONT structure 41 | LOGFONT logical_font; 42 | logical_font.lfHeight = get_point_size(); 43 | logical_font.lfWidth = 0; 44 | logical_font.lfEscapement = 0; 45 | logical_font.lfOrientation = 0; 46 | logical_font.lfWeight = FW_NORMAL; 47 | logical_font.lfItalic = false; 48 | logical_font.lfUnderline = false; 49 | logical_font.lfStrikeOut = false; 50 | logical_font.lfCharSet = ANSI_CHARSET; 51 | logical_font.lfOutPrecision = OUT_DEFAULT_PRECIS; 52 | logical_font.lfClipPrecision = CLIP_DEFAULT_PRECIS; 53 | logical_font.lfQuality = PROOF_QUALITY; 54 | logical_font.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; 55 | lstrcpy(logical_font.lfFaceName, fontname.c_str()); 56 | 57 | font = CreateFontIndirect(&logical_font); 58 | 59 | HFONT previous_font = (HFONT)SelectObject(c, font); 60 | 61 | wglUseFontBitmaps(c, 0, 128, next_call_list_start); 62 | font_size_lookup[in_size] = next_call_list_start; 63 | font_handle_lookup[in_size] = font; 64 | next_call_list_start += 130; 65 | 66 | SelectObject(c, previous_font); 67 | } 68 | 69 | #else 70 | 71 | // TODO: is this sufficient? 72 | point_size = size; 73 | 74 | if (font_size_lookup[size] == 0) 75 | { 76 | int list_start = glGenLists(128); 77 | 78 | // MACNOTE: Force Trebuchet MS. It's what we mostly use anyway, but 79 | // I want to be sure they have it. 80 | const CFStringRef font_name = CFSTR("Trebuchet MS"); 81 | 82 | ATSFontFamilyRef font = ATSFontFamilyFindFromName(font_name, kATSOptionFlagsDefault); 83 | if (!font) throw PianoGameError(WSTRING(L"Couldn't get ATSFontFamilyRef for font '" << WideFromMacString(font_name) << L"'.")); 84 | 85 | AGLContext context = aglGetCurrentContext(); 86 | if (!context) throw PianoGameError(L"Couldn't retrieve OpenGL context while creating font."); 87 | 88 | GLboolean ret = aglUseFont(context, font, normal, size, 0, 128, list_start); 89 | if (ret == GL_FALSE) throw PianoGameError(WSTRING(L"aglUseFont() call failed with error code: " << aglGetError())); 90 | 91 | font_size_lookup[size] = list_start; 92 | 93 | 94 | // Create the ATSU style object that we'll use for calculating text extents and store it for later. 95 | ATSUStyle style; 96 | 97 | OSStatus status = ATSUCreateStyle(&style); 98 | if (status != noErr) throw PianoGameError(WSTRING(L"Couldn't create ATSU style. Error code: " << static_cast(status))); 99 | 100 | Fixed fixed_size = Long2Fix(size); 101 | 102 | ATSUAttributeTag tags[] = { kATSUSizeTag }; 103 | ByteCount sizes[] = { sizeof(Fixed) }; 104 | ATSUAttributeValuePtr values[] = { &fixed_size }; 105 | status = ATSUSetAttributes(style, sizeof(sizes) / sizeof(ByteCount), tags, sizes, values); 106 | if (status != noErr) throw PianoGameError(WSTRING(L"Couldn't set ATSU style attributes. Error code: " << static_cast(status))); 107 | 108 | atsu_style_lookup[size] = style; 109 | } 110 | 111 | #endif 112 | 113 | } 114 | 115 | TextWriter::~TextWriter() 116 | { 117 | } 118 | 119 | int TextWriter::get_point_size() 120 | { 121 | return point_size; 122 | } 123 | 124 | TextWriter& TextWriter::next_line() 125 | { 126 | y += std::max(last_line_height, get_point_size()); 127 | x = original_x; 128 | 129 | last_line_height = 0; 130 | return *this; 131 | } 132 | 133 | TextWriter& Text::operator<<(TextWriter& tw) const 134 | { 135 | int draw_x; 136 | int draw_y; 137 | calculate_position_and_advance_cursor(tw, &draw_x, &draw_y); 138 | 139 | // TODO: This isn't Unicode! 140 | std::string narrow(m_text.begin(), m_text.end()); 141 | 142 | glBindTexture(GL_TEXTURE_2D, 0); 143 | 144 | glPushMatrix(); 145 | tw.renderer.SetColor(m_color); 146 | glListBase(font_size_lookup[tw.size]); 147 | glRasterPos2i(draw_x, draw_y + tw.size); 148 | glCallLists(static_cast(narrow.length()), GL_UNSIGNED_BYTE, narrow.c_str()); 149 | glPopMatrix(); 150 | 151 | // TODO: Should probably delete these on shutdown. 152 | //glDeleteLists(1000, 128); 153 | 154 | return tw; 155 | } 156 | 157 | void Text::calculate_position_and_advance_cursor(TextWriter &tw, int *out_x, int *out_y) const 158 | { 159 | #ifdef WIN32 160 | 161 | const long options = DT_LEFT | DT_NOPREFIX; 162 | 163 | Context c = tw.renderer.m_context; 164 | int previous_map_mode = SetMapMode(c, MM_TEXT); 165 | 166 | HFONT font = font_handle_lookup[tw.size]; 167 | 168 | // Create the font we want to use, and swap it out with 169 | // whatever is currently in there, along with our color 170 | HFONT previous_font = (HFONT)SelectObject(c, font); 171 | 172 | // Call DrawText to find out how large our text is 173 | RECT drawing_rect = { tw.x, tw.y, 0, 0 }; 174 | tw.last_line_height = DrawText(c, m_text.c_str(), int(m_text.length()), &drawing_rect, options | DT_CALCRECT); 175 | 176 | // Return the hdc settings to their previous setting 177 | SelectObject(c, previous_font); 178 | SetMapMode(c, previous_map_mode); 179 | 180 | #else 181 | 182 | // Convert passed-in text to Unicode 183 | CFStringRef cftext = MacStringFromWide(m_text, true).get(); 184 | CFDataRef unitext = CFStringCreateExternalRepresentation(kCFAllocatorDefault, cftext, kCFStringEncodingUnicode, 0); 185 | if (!unitext) throw PianoGameError(WSTRING(L"Couldn't convert string to unicode: '" << m_text << L"'")); 186 | CFRelease(cftext); 187 | 188 | // Create an ATSU layout 189 | ATSUTextLayout layout; 190 | const UniCharCount run_length = kATSUToTextEnd; 191 | OSStatus status = ATSUCreateTextLayoutWithTextPtr((ConstUniCharArrayPtr)CFDataGetBytePtr(unitext), kATSUFromTextBeginning, kATSUToTextEnd, CFDataGetLength(unitext) / 2, 1, &run_length, &atsu_style_lookup[tw.size], &layout); 192 | if (status != noErr) throw PianoGameError(WSTRING(L"Couldn't create ATSU text layout for string: '" << m_text << L"', Error code: " << static_cast(status))); 193 | 194 | // Measure the size of the resulting text 195 | Rect drawing_rect = { 0, 0, 0, 0 }; 196 | 197 | ATSUTextMeasurement before = 0; 198 | ATSUTextMeasurement after = 0; 199 | ATSUTextMeasurement ascent = 0; 200 | ATSUTextMeasurement descent = 0; 201 | 202 | status = ATSUGetUnjustifiedBounds(layout, 0, kATSUToTextEnd, &before, &after, &ascent, &descent); 203 | if (status != noErr) throw PianoGameError(WSTRING(L"Couldn't get unjustified bounds for text layout for string: '" << m_text << L"', Error code: " << static_cast(status))); 204 | 205 | // NOTE: the +1 here is completely arbitrary and seemed to place the text better. 206 | // It may just be a difference between the Windows and Mac text placement systems. 207 | drawing_rect.top += tw.y + 1; 208 | drawing_rect.left += tw.x + FixRound(before); 209 | drawing_rect.right += tw.x + FixRound(after); 210 | 211 | // Not used. 212 | drawing_rect.bottom = 0; 213 | 214 | // Clean-up 215 | ATSUDisposeTextLayout(layout); 216 | CFRelease(unitext); 217 | 218 | #endif 219 | 220 | // Update the text-writer with post-draw coordinates 221 | if (tw.centered) drawing_rect.left -= (drawing_rect.right - drawing_rect.left) / 2; 222 | if (!tw.centered) tw.x += drawing_rect.right - drawing_rect.left; 223 | 224 | // Tell the draw function where to put the text 225 | *out_x = drawing_rect.left; 226 | *out_y = drawing_rect.top; 227 | } 228 | 229 | TextWriter& operator<<(TextWriter& tw, const Text& t) 230 | { 231 | return t.operator <<(tw); 232 | } 233 | 234 | TextWriter& newline(TextWriter& tw) 235 | { 236 | return tw.next_line(); 237 | } 238 | 239 | TextWriter& operator<<(TextWriter& tw, const std::wstring& s) { return tw << Text(s, White); } 240 | TextWriter& operator<<(TextWriter& tw, const int& i) { return tw << Text(i, White); } 241 | TextWriter& operator<<(TextWriter& tw, const unsigned int& i) { return tw << Text(i, White); } 242 | TextWriter& operator<<(TextWriter& tw, const long& l) { return tw << Text(l, White); } 243 | TextWriter& operator<<(TextWriter& tw, const unsigned long& l) { return tw << Text(l, White); } 244 | -------------------------------------------------------------------------------- /src/libmidi/MidiEvent.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "MidiEvent.h" 6 | #include "MidiUtil.h" 7 | #include "Note.h" 8 | 9 | #include "../string_util.h" 10 | using namespace std; 11 | 12 | MidiEvent MidiEvent::ReadFromStream(istream &stream, unsigned char last_status, bool contains_delta_pulses) 13 | { 14 | MidiEvent ev; 15 | 16 | if (contains_delta_pulses) ev.m_delta_pulses = parse_variable_length(stream); 17 | else ev.m_delta_pulses = 0; 18 | 19 | // MIDI uses a compression mechanism called "running status". 20 | // Anytime you read a status byte that doesn't have the highest- 21 | // order bit set, what you actually read is the 1st data byte 22 | // of a message with the status of the previous message. 23 | ev.m_status = static_cast(stream.peek()); 24 | if ((ev.m_status & 0x80) == 0) 25 | { 26 | ev.m_status = last_status; 27 | } 28 | else 29 | { 30 | // It was a status byte after all, just read past it 31 | // in the stream 32 | stream.read(reinterpret_cast(&ev.m_status), sizeof(unsigned char)); 33 | } 34 | 35 | switch (ev.Type()) 36 | { 37 | case MidiEventType_Meta: ev.ReadMeta(stream); break; 38 | case MidiEventType_SysEx: ev.ReadSysEx(stream); break; 39 | default: ev.ReadStandard(stream); break; 40 | } 41 | 42 | return ev; 43 | } 44 | 45 | MidiEvent MidiEvent::Build(const MidiEventSimple &simple) 46 | { 47 | MidiEvent ev; 48 | 49 | ev.m_delta_pulses = 0; 50 | ev.m_status = simple.status; 51 | ev.m_data1 = simple.byte1; 52 | ev.m_data2 = simple.byte2; 53 | if (ev.Type() == MidiEventType_Meta) throw MidiError(MidiError_MetaEventOnInput); 54 | 55 | return ev; 56 | } 57 | 58 | MidiEvent MidiEvent::NullEvent() 59 | { 60 | MidiEvent ev; 61 | ev.m_status = 0xFF; 62 | ev.m_meta_type = MidiMetaEvent_Proprietary; 63 | ev.m_delta_pulses = 0; 64 | 65 | return ev; 66 | } 67 | 68 | void MidiEvent::ReadMeta(std::istream &stream) 69 | { 70 | stream.read(reinterpret_cast(&m_meta_type), sizeof(unsigned char)); 71 | unsigned long meta_length = parse_variable_length(stream); 72 | 73 | char *buffer = new char[meta_length + 1]; 74 | buffer[meta_length] = 0; 75 | 76 | stream.read(buffer, meta_length); 77 | if (stream.fail()) 78 | { 79 | delete[] buffer; 80 | throw MidiError(MidiError_EventTooShort); 81 | } 82 | 83 | switch (m_meta_type) 84 | { 85 | case MidiMetaEvent_Text: 86 | case MidiMetaEvent_Copyright: 87 | case MidiMetaEvent_TrackName: 88 | case MidiMetaEvent_Instrument: 89 | case MidiMetaEvent_Lyric: 90 | case MidiMetaEvent_Marker: 91 | case MidiMetaEvent_Cue: 92 | case MidiMetaEvent_PatchName: 93 | case MidiMetaEvent_DeviceName: 94 | m_text = string(buffer, meta_length); 95 | break; 96 | 97 | case MidiMetaEvent_TempoChange: 98 | { 99 | if (meta_length < 3) throw MidiError(MidiError_EventTooShort); 100 | 101 | // We have to convert to unsigned char first for some reason or the 102 | // conversion gets all wacky and tries to look at more than just its 103 | // one byte at a time. 104 | unsigned int b0 = static_cast(buffer[0]); 105 | unsigned int b1 = static_cast(buffer[1]); 106 | unsigned int b2 = static_cast(buffer[2]); 107 | m_tempo_uspqn = (b0 << 16) + (b1 << 8) + b2; 108 | } 109 | break; 110 | 111 | 112 | case MidiMetaEvent_SequenceNumber: 113 | case MidiMetaEvent_EndOfTrack: 114 | case MidiMetaEvent_SMPTEOffset: 115 | case MidiMetaEvent_TimeSignature: 116 | case MidiMetaEvent_KeySignature: 117 | case MidiMetaEvent_Proprietary: 118 | 119 | case MidiMetaEvent_ChannelPrefix: 120 | case MidiMetaEvent_MidiPort: 121 | // NOTE: We would have to keep all of this around if we 122 | // wanted to reproduce 1:1 MIDIs between file Save/Load 123 | break; 124 | 125 | default: 126 | { 127 | delete[] buffer; 128 | throw MidiError(MidiError_UnknownMetaEventType); 129 | } 130 | } 131 | 132 | delete[] buffer; 133 | } 134 | 135 | void MidiEvent::ReadSysEx(std::istream &stream) 136 | { 137 | // NOTE: We would have to keep SysEx events around if we 138 | // wanted to reproduce 1:1 MIDIs between file Save/Load 139 | unsigned long sys_ex_length = parse_variable_length(stream); 140 | 141 | // Discard 142 | char *buffer = new char[sys_ex_length]; 143 | stream.read(buffer, sys_ex_length); 144 | delete[] buffer; 145 | } 146 | 147 | void MidiEvent::ReadStandard(std::istream &stream) 148 | { 149 | switch (Type()) 150 | { 151 | case MidiEventType_NoteOff: 152 | case MidiEventType_NoteOn: 153 | case MidiEventType_Aftertouch: 154 | case MidiEventType_Controller: 155 | case MidiEventType_PitchWheel: 156 | { 157 | stream.read(reinterpret_cast(&m_data1), sizeof(unsigned char)); 158 | stream.read(reinterpret_cast(&m_data2), sizeof(unsigned char)); 159 | } 160 | break; 161 | 162 | case MidiEventType_ProgramChange: 163 | case MidiEventType_ChannelPressure: 164 | { 165 | stream.read(reinterpret_cast(&m_data1), sizeof(unsigned char)); 166 | m_data2 = 0; 167 | } 168 | break; 169 | 170 | default: 171 | throw MidiError(MidiError_UnknownEventType); 172 | } 173 | } 174 | 175 | bool MidiEvent::GetSimpleEvent(MidiEventSimple *simple) const 176 | { 177 | MidiEventType t = Type(); 178 | if (t == MidiEventType_Meta || t == MidiEventType_SysEx || t == MidiEventType_Unknown) return false; 179 | 180 | simple->status = m_status; 181 | simple->byte1 = m_data1; 182 | simple->byte2 = m_data2; 183 | 184 | return true; 185 | } 186 | 187 | MidiEventType MidiEvent::Type() const 188 | { 189 | if (m_status > 0xEF && m_status < 0xFF) return MidiEventType_SysEx; 190 | if (m_status < 0x80) return MidiEventType_Unknown; 191 | if (m_status == 0xFF) return MidiEventType_Meta; 192 | 193 | // The 0x8_ through 0xE_ events contain channel numbers 194 | // in the lowest 4 bits 195 | unsigned char status_top = m_status >> 4; 196 | 197 | switch (status_top) 198 | { 199 | case 0x8: return MidiEventType_NoteOff; 200 | case 0x9: return MidiEventType_NoteOn; 201 | case 0xA: return MidiEventType_Aftertouch; 202 | case 0xB: return MidiEventType_Controller; 203 | case 0xC: return MidiEventType_ProgramChange; 204 | case 0xD: return MidiEventType_ChannelPressure; 205 | case 0xE: return MidiEventType_PitchWheel; 206 | 207 | default: return MidiEventType_Unknown; 208 | } 209 | } 210 | 211 | MidiMetaEventType MidiEvent::MetaType() const 212 | { 213 | if (Type() != MidiEventType_Meta) return MidiMetaEvent_Unknown; 214 | 215 | return static_cast(m_meta_type); 216 | } 217 | 218 | bool MidiEvent::IsEnd() const 219 | { 220 | return (Type() == MidiEventType_Meta && MetaType() == MidiMetaEvent_EndOfTrack); 221 | } 222 | 223 | unsigned char MidiEvent::Channel() const 224 | { 225 | // The channel is held in the lower nibble of the status code 226 | return (m_status & 0x0F); 227 | } 228 | 229 | void MidiEvent::SetChannel(unsigned char channel) 230 | { 231 | if (channel > 15) return; 232 | 233 | // Clear out the old channel 234 | m_status = m_status & 0xF0; 235 | 236 | // Set the new channel 237 | m_status = m_status | channel; 238 | } 239 | 240 | void MidiEvent::SetVelocity(int velocity) 241 | { 242 | if (Type() != MidiEventType_NoteOn) return; 243 | 244 | m_data2 = static_cast(velocity); 245 | } 246 | 247 | bool MidiEvent::HasText() const 248 | { 249 | if (Type() != MidiEventType_Meta) return false; 250 | 251 | switch (m_meta_type) 252 | { 253 | case MidiMetaEvent_Text: 254 | case MidiMetaEvent_Copyright: 255 | case MidiMetaEvent_TrackName: 256 | case MidiMetaEvent_Instrument: 257 | case MidiMetaEvent_Lyric: 258 | case MidiMetaEvent_Marker: 259 | case MidiMetaEvent_Cue: 260 | case MidiMetaEvent_PatchName: 261 | case MidiMetaEvent_DeviceName: 262 | return true; 263 | 264 | default: 265 | return false; 266 | } 267 | } 268 | 269 | NoteId MidiEvent::NoteNumber() const 270 | { 271 | if (Type() != MidiEventType_NoteOn && Type() != MidiEventType_NoteOff) return 0; 272 | return m_data1; 273 | } 274 | 275 | void MidiEvent::ShiftNote(int shift_amount) 276 | { 277 | if (Type() != MidiEventType_NoteOn && Type() != MidiEventType_NoteOff) return; 278 | m_data1 = m_data1 + static_cast(shift_amount); 279 | } 280 | 281 | int MidiEvent::ProgramNumber() const 282 | { 283 | if (Type() != MidiEventType_ProgramChange) return 0; 284 | return m_data1; 285 | } 286 | 287 | std::string MidiEvent::NoteName(unsigned int note_number) 288 | { 289 | // Music domain knowledge 290 | const static unsigned int NotesPerOctave = 12; 291 | const static string NoteBases[NotesPerOctave] = { 292 | STRING("C"), STRING("C#"), STRING("D"), 293 | STRING("D#"), STRING("E"), STRING("F"), 294 | STRING("F#"), STRING("G"), STRING("G#"), 295 | STRING("A"), STRING("A#"), STRING("B") 296 | }; 297 | 298 | unsigned int octave = (note_number / NotesPerOctave); 299 | const string note_base = NoteBases[note_number % NotesPerOctave]; 300 | 301 | return STRING(note_base << octave); 302 | } 303 | 304 | int MidiEvent::NoteVelocity() const 305 | { 306 | if (Type() == MidiEventType_NoteOff) return 0; 307 | if (Type() != MidiEventType_NoteOn) return -1; 308 | return static_cast(m_data2); 309 | } 310 | 311 | std::string MidiEvent::Text() const 312 | { 313 | if (!HasText()) return ""; 314 | return m_text; 315 | } 316 | 317 | unsigned long MidiEvent::GetTempoInUsPerQn() const 318 | { 319 | if (Type() != MidiEventType_Meta || MetaType() != MidiMetaEvent_TempoChange) 320 | { 321 | throw MidiError(MidiError_RequestedTempoFromNonTempoEvent); 322 | } 323 | 324 | return m_tempo_uspqn; 325 | } 326 | -------------------------------------------------------------------------------- /src/libmidi/MidiUtil.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "MidiUtil.h" 6 | #include "../string_util.h" 7 | 8 | #ifndef WIN32 9 | #include 10 | #endif 11 | 12 | using namespace std; 13 | 14 | unsigned long BigToSystem32(unsigned long x) 15 | { 16 | #ifdef WIN32 17 | return ((((x) & 0x00ff0000) >> 8 ) | 18 | (( (x) & 0x0000ff00) << 8 ) | 19 | (( (x) & 0xff000000) >> 24) | 20 | (( (x) & 0x000000ff) << 24)); 21 | #else 22 | return CFSwapInt32BigToHost(x); 23 | #endif 24 | } 25 | 26 | unsigned short BigToSystem16(unsigned short x) 27 | { 28 | #ifdef WIN32 29 | return ((((x) & 0xff00) >> 8) | 30 | (( (x) & 0x00ff) << 8)); 31 | #else 32 | return CFSwapInt16BigToHost(x); 33 | #endif 34 | } 35 | 36 | 37 | 38 | unsigned long parse_variable_length(istream &in) 39 | { 40 | register unsigned long value = in.get(); 41 | 42 | if (in.good() && (value & 0x80) ) 43 | { 44 | value &= 0x7F; 45 | 46 | register unsigned long c; 47 | do 48 | { 49 | c = in.get(); 50 | value = (value << 7) + (c & 0x7F); 51 | } while (in.good() && (c & 0x80) ); 52 | } 53 | 54 | return(value); 55 | } 56 | 57 | std::wstring MidiError::GetErrorDescription() const 58 | { 59 | switch (m_error) 60 | { 61 | case MidiError_UnknownHeaderType: return L"Found an unknown header type.\n\nThis probably isn't a valid MIDI file."; 62 | case MidiError_BadFilename: return L"Could not open file for input. Check that file exists."; 63 | case MidiError_NoHeader: return L"No MIDI header could be read. File too short."; 64 | case MidiError_BadHeaderSize: return L"Incorrect header size."; 65 | case MidiError_Type2MidiNotSupported: return L"Type 2 MIDI is not supported."; 66 | case MidiError_BadType0Midi: return L"Type 0 MIDI should only have 1 track."; 67 | case MidiError_SMTPETimingNotImplemented: return L"MIDI using SMTP time division is not implemented."; 68 | 69 | case MidiError_BadTrackHeaderType: return L"Found an unknown track header type."; 70 | case MidiError_TrackHeaderTooShort: return L"File terminated before reading track header."; 71 | case MidiError_TrackTooShort: return L"Data stream too short to read entire track."; 72 | case MidiError_BadTrackEnd: return L"MIDI track did not end with End-Of-Track event."; 73 | 74 | case MidiError_EventTooShort: return L"Data stream ended before reported end of MIDI event."; 75 | case MidiError_UnknownEventType: return L"Found an unknown MIDI Event Type."; 76 | case MidiError_UnknownMetaEventType: return L"Found an unknown MIDI Meta Event Type."; 77 | 78 | case MidiError_MM_NoDevice: return L"Could not open the specified MIDI device."; 79 | case MidiError_MM_NotEnabled: return L"MIDI device failed enable."; 80 | case MidiError_MM_AlreadyAllocated: return L"The specified MIDI device is already in use."; 81 | case MidiError_MM_BadDeviceID: return L"The MIDI device ID specified is out of range."; 82 | case MidiError_MM_InvalidParameter: return L"An invalid parameter was used with the MIDI device."; 83 | case MidiError_MM_NoDriver: return L"The specified MIDI driver is not installed."; 84 | case MidiError_MM_NoMemory: return L"Cannot allocate or lock memory for MIDI device."; 85 | case MidiError_MM_Unknown: return L"An unknown MIDI I/O error has occurred."; 86 | 87 | case MidiError_NoInputAvailable: return L"Attempted to read MIDI event from an empty input buffer."; 88 | case MidiError_MetaEventOnInput: return L"MIDI Input device sent a Meta Event."; 89 | 90 | case MidiError_InputError: return L"MIDI input driver reported an error."; 91 | case MidiError_InvalidInputErrorBehavior: return L"Invalid InputError value. Choices are 'report', 'ignore', and 'use'."; 92 | 93 | case MidiError_RequestedTempoFromNonTempoEvent: return L"Tempo data was requested from a non-tempo MIDI event."; 94 | 95 | default: return WSTRING(L"Unknown MidiError Code (" << m_error << L")."); 96 | } 97 | } 98 | 99 | std::wstring GetMidiEventTypeDescription(MidiEventType type) 100 | { 101 | switch (type) 102 | { 103 | case MidiEventType_Meta: return L"Meta"; 104 | case MidiEventType_SysEx: return L"System Exclusive"; 105 | 106 | case MidiEventType_NoteOff: return L"Note-Off"; 107 | case MidiEventType_NoteOn: return L"Note-On"; 108 | case MidiEventType_Aftertouch: return L"Aftertouch"; 109 | case MidiEventType_Controller: return L"Controller"; 110 | case MidiEventType_ProgramChange: return L"Program Change"; 111 | case MidiEventType_ChannelPressure: return L"Channel Pressure"; 112 | case MidiEventType_PitchWheel: return L"Pitch Wheel"; 113 | 114 | case MidiEventType_Unknown: return L"Unknown"; 115 | default: return L"BAD EVENT TYPE"; 116 | } 117 | 118 | } 119 | 120 | std::wstring GetMidiMetaEventTypeDescription(MidiMetaEventType type) 121 | { 122 | switch (type) 123 | { 124 | case MidiMetaEvent_SequenceNumber: return L"Sequence Number"; 125 | 126 | case MidiMetaEvent_Text: return L"Text"; 127 | case MidiMetaEvent_Copyright: return L"Copyright"; 128 | case MidiMetaEvent_TrackName: return L"Track Name"; 129 | case MidiMetaEvent_Instrument: return L"Instrument"; 130 | case MidiMetaEvent_Lyric: return L"Lyric"; 131 | case MidiMetaEvent_Marker: return L"Marker"; 132 | case MidiMetaEvent_Cue: return L"Cue Point"; 133 | case MidiMetaEvent_PatchName: return L"Patch Name"; 134 | case MidiMetaEvent_DeviceName: return L"Device Name"; 135 | 136 | case MidiMetaEvent_EndOfTrack: return L"End Of Track"; 137 | case MidiMetaEvent_TempoChange: return L"Tempo Change"; 138 | case MidiMetaEvent_SMPTEOffset: return L"SMPTE Offset"; 139 | case MidiMetaEvent_TimeSignature: return L"Time Signature"; 140 | case MidiMetaEvent_KeySignature: return L"Key Signature"; 141 | 142 | case MidiMetaEvent_Proprietary: return L"Proprietary"; 143 | 144 | case MidiMetaEvent_ChannelPrefix: return L"(Deprecated) Channel Prefix"; 145 | case MidiMetaEvent_MidiPort: return L"(Deprecated) MIDI Port"; 146 | 147 | case MidiMetaEvent_Unknown: return L"Unknown Meta Event Type"; 148 | default: return L"BAD META EVENT TYPE"; 149 | } 150 | } 151 | 152 | std::wstring const InstrumentNames[InstrumentCount] = { 153 | L"Acoustic Grand Piano", 154 | L"Bright Acoustic Piano", 155 | L"Electric Grand Piano", 156 | L"Honky-tonk Piano", 157 | L"Electric Piano 1", 158 | L"Electric Piano 2", 159 | L"Harpsichord", 160 | L"Clavi", 161 | L"Celesta", 162 | L"Glockenspiel", 163 | L"Music Box", 164 | L"Vibraphone", 165 | L"Marimba", 166 | L"Xylophone", 167 | L"Tubular Bells", 168 | L"Dulcimer", 169 | L"Drawbar Organ", 170 | L"Percussive Organ", 171 | L"Rock Organ", 172 | L"Church Organ", 173 | L"Reed Organ", 174 | L"Accordion", 175 | L"Harmonica", 176 | L"Tango Accordion", 177 | L"Acoustic Guitar (nylon)", 178 | L"Acoustic Guitar (steel)", 179 | L"Electric Guitar (jazz)", 180 | L"Electric Guitar (clean)", 181 | L"Electric Guitar (muted)", 182 | L"Overdriven Guitar", 183 | L"Distortion Guitar", 184 | L"Guitar harmonics", 185 | L"Acoustic Bass", 186 | L"Electric Bass (finger)", 187 | L"Electric Bass (pick)", 188 | L"Fretless Bass", 189 | L"Slap Bass 1", 190 | L"Slap Bass 2", 191 | L"Synth Bass 1", 192 | L"Synth Bass 2", 193 | L"Violin", 194 | L"Viola", 195 | L"Cello", 196 | L"Contrabass", 197 | L"Tremolo Strings", 198 | L"Pizzicato Strings", 199 | L"Orchestral Harp", 200 | L"Timpani", 201 | L"String Ensemble 1", 202 | L"String Ensemble 2", 203 | L"SynthStrings 1", 204 | L"SynthStrings 2", 205 | L"Choir Aahs", 206 | L"Voice Oohs", 207 | L"Synth Voice", 208 | L"Orchestra Hit", 209 | L"Trumpet", 210 | L"Trombone", 211 | L"Tuba", 212 | L"Muted Trumpet", 213 | L"French Horn", 214 | L"Brass Section", 215 | L"SynthBrass 1", 216 | L"SynthBrass 2", 217 | L"Soprano Sax", 218 | L"Alto Sax", 219 | L"Tenor Sax", 220 | L"Baritone Sax", 221 | L"Oboe", 222 | L"English Horn", 223 | L"Bassoon", 224 | L"Clarinet", 225 | L"Piccolo", 226 | L"Flute", 227 | L"Recorder", 228 | L"Pan Flute", 229 | L"Blown Bottle", 230 | L"Shakuhachi", 231 | L"Whistle", 232 | L"Ocarina", 233 | L"Lead 1 (square)", 234 | L"Lead 2 (sawtooth)", 235 | L"Lead 3 (calliope)", 236 | L"Lead 4 (chiff)", 237 | L"Lead 5 (charang)", 238 | L"Lead 6 (voice)", 239 | L"Lead 7 (fifths)", 240 | L"Lead 8 (bass + lead)", 241 | L"Pad 1 (new age)", 242 | L"Pad 2 (warm)", 243 | L"Pad 3 (polysynth)", 244 | L"Pad 4 (choir)", 245 | L"Pad 5 (bowed)", 246 | L"Pad 6 (metallic)", 247 | L"Pad 7 (halo)", 248 | L"Pad 8 (sweep)", 249 | L"FX 1 (rain)", 250 | L"FX 2 (soundtrack)", 251 | L"FX 3 (crystal)", 252 | L"FX 4 (atmosphere)", 253 | L"FX 5 (brightness)", 254 | L"FX 6 (goblins)", 255 | L"FX 7 (echoes)", 256 | L"FX 8 (sci-fi)", 257 | L"Sitar", 258 | L"Banjo", 259 | L"Shamisen", 260 | L"Koto", 261 | L"Kalimba", 262 | L"Bag pipe", 263 | L"Fiddle", 264 | L"Shanai", 265 | L"Tinkle Bell", 266 | L"Agogo", 267 | L"Steel Drums", 268 | L"Woodblock", 269 | L"Taiko Drum", 270 | L"Melodic Tom", 271 | L"Synth Drum", 272 | L"Reverse Cymbal", 273 | L"Guitar Fret Noise", 274 | L"Breath Noise", 275 | L"Seashore", 276 | L"Bird Tweet", 277 | L"Telephone Ring", 278 | L"Helicopter", 279 | L"Applause", 280 | L"Gunshot", 281 | 282 | // 283 | // NOTE: These aren't actually General MIDI instruments! 284 | // 285 | L"Percussion", // for Tracks that use Channel 10 or 16 286 | L"Various" // for Tracks that use more than one 287 | }; 288 | -------------------------------------------------------------------------------- /PianoGame.vcproj: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 26 | 29 | 32 | 35 | 38 | 41 | 55 | 58 | 61 | 64 | 74 | 77 | 80 | 83 | 86 | 89 | 92 | 95 | 96 | 104 | 107 | 110 | 113 | 116 | 119 | 135 | 138 | 141 | 144 | 157 | 160 | 163 | 166 | 169 | 172 | 175 | 178 | 179 | 180 | 181 | 182 | 183 | 186 | 189 | 190 | 193 | 194 | 197 | 198 | 201 | 202 | 205 | 206 | 209 | 210 | 213 | 214 | 217 | 218 | 221 | 222 | 225 | 226 | 229 | 232 | 233 | 236 | 237 | 240 | 241 | 244 | 245 | 248 | 249 | 252 | 253 | 256 | 257 | 258 | 261 | 264 | 265 | 268 | 269 | 272 | 273 | 276 | 277 | 280 | 281 | 284 | 285 | 288 | 289 | 292 | 293 | 296 | 297 | 300 | 301 | 302 | 305 | 308 | 309 | 312 | 313 | 316 | 317 | 320 | 321 | 324 | 325 | 328 | 329 | 332 | 333 | 336 | 337 | 338 | 341 | 344 | 345 | 348 | 349 | 352 | 353 | 356 | 357 | 360 | 361 | 364 | 365 | 368 | 369 | 372 | 373 | 376 | 377 | 380 | 381 | 382 | 385 | 388 | 389 | 392 | 393 | 396 | 397 | 400 | 401 | 404 | 405 | 408 | 409 | 412 | 413 | 416 | 417 | 420 | 421 | 424 | 425 | 428 | 429 | 432 | 433 | 436 | 437 | 440 | 441 | 442 | 443 | 446 | 449 | 450 | 453 | 454 | 457 | 458 | 461 | 462 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | -------------------------------------------------------------------------------- /src/State_TrackSelection.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2007 Nicholas Piegdon 3 | // See license.txt for license information 4 | 5 | #include "State_TrackSelection.h" 6 | 7 | #include "State_Title.h" 8 | #include "State_Playing.h" 9 | #include "MenuLayout.h" 10 | #include "Renderer.h" 11 | #include "Textures.h" 12 | 13 | #include "libmidi/Midi.h" 14 | #include "libmidi/MidiUtil.h" 15 | #include "libmidi/MidiComm.h" 16 | 17 | TrackSelectionState::TrackSelectionState(const SharedState &state) 18 | : m_state(state), m_preview_on(false), m_preview_track_id(0), 19 | m_first_update_after_seek(false), 20 | m_page_count(0), m_current_page(0), m_tiles_per_page(0) 21 | { } 22 | 23 | void TrackSelectionState::Init() 24 | { 25 | if (m_state.midi_out) m_state.midi_out->Reset(); 26 | 27 | Midi &m = *m_state.midi; 28 | 29 | // Prepare a very simple count of the playable tracks first 30 | int track_count = 0; 31 | for (size_t i = 0; i < m.Tracks().size(); ++i) 32 | { 33 | if (m.Tracks()[i].Notes().size()) track_count++; 34 | } 35 | 36 | m_back_button = ButtonState(Layout::ScreenMarginX, 37 | GetStateHeight() - Layout::ScreenMarginY/2 - Layout::ButtonHeight/2, 38 | Layout::ButtonWidth, Layout::ButtonHeight); 39 | 40 | m_continue_button = ButtonState(GetStateWidth() - Layout::ScreenMarginX - Layout::ButtonWidth, 41 | GetStateHeight() - Layout::ScreenMarginY/2 - Layout::ButtonHeight/2, 42 | Layout::ButtonWidth, Layout::ButtonHeight); 43 | 44 | // Determine how many track tiles we can fit 45 | // horizontally and vertically. Integer division 46 | // helps us round down here. 47 | int tiles_across = (GetStateWidth() + Layout::ScreenMarginX) / (TrackTileWidth + Layout::ScreenMarginX); 48 | tiles_across = std::max(tiles_across, 1); 49 | 50 | int tiles_down = (GetStateHeight() - Layout::ScreenMarginX - Layout::ScreenMarginY * 2) / (TrackTileHeight + Layout::ScreenMarginX); 51 | tiles_down = std::max(tiles_down, 1); 52 | 53 | // Calculate how many pages of tracks there will be 54 | m_tiles_per_page = tiles_across * tiles_down; 55 | 56 | m_page_count = track_count / m_tiles_per_page; 57 | const int remainder = track_count % m_tiles_per_page; 58 | if (remainder > 0) m_page_count++; 59 | 60 | // If we have fewer than one row of tracks, just 61 | // center the tracks we do have 62 | if (track_count < tiles_across) tiles_across = track_count; 63 | 64 | // Determine how wide that many track tiles will 65 | // actually be, so we can center the list 66 | int all_tile_widths = tiles_across * TrackTileWidth + (tiles_across-1) * Layout::ScreenMarginX; 67 | int global_x_offset = (GetStateWidth() - all_tile_widths) / 2; 68 | 69 | const static int starting_y = 100; 70 | 71 | int tiles_on_this_line = 0; 72 | int tiles_on_this_page = 0; 73 | int current_y = starting_y; 74 | for (size_t i = 0; i < m.Tracks().size(); ++i) 75 | { 76 | const MidiTrack &t = m.Tracks()[i]; 77 | if (t.Notes().size() == 0) continue; 78 | 79 | int x = global_x_offset + (TrackTileWidth + Layout::ScreenMarginX)*tiles_on_this_line; 80 | int y = current_y; 81 | 82 | Track::Mode mode = Track::ModePlayedAutomatically; 83 | if (t.IsPercussion()) mode = Track::ModePlayedButHidden; 84 | 85 | Track::TrackColor color = static_cast((m_track_tiles.size()) % Track::UserSelectableColorCount); 86 | 87 | // If we came back here from StatePlaying, reload all our preferences 88 | if (m_state.track_properties.size() > i) 89 | { 90 | color = m_state.track_properties[i].color; 91 | mode = m_state.track_properties[i].mode; 92 | } 93 | 94 | TrackTile tile(x, y, i, color, mode); 95 | 96 | m_track_tiles.push_back(tile); 97 | 98 | 99 | tiles_on_this_line++; 100 | tiles_on_this_line %= tiles_across; 101 | if (!tiles_on_this_line) 102 | { 103 | current_y += TrackTileHeight + Layout::ScreenMarginX; 104 | } 105 | 106 | tiles_on_this_page++; 107 | tiles_on_this_page %= m_tiles_per_page; 108 | if (!tiles_on_this_page) 109 | { 110 | current_y = starting_y; 111 | tiles_on_this_line = 0; 112 | } 113 | } 114 | } 115 | 116 | std::vector TrackSelectionState::BuildTrackProperties() const 117 | { 118 | std::vector props; 119 | for (size_t i = 0; i < m_state.midi->Tracks().size(); ++i) 120 | { 121 | props.push_back(Track::Properties()); 122 | } 123 | 124 | // Populate it with the tracks that have notes 125 | for (std::vector::const_iterator i = m_track_tiles.begin(); i != m_track_tiles.end(); ++i) 126 | { 127 | props[i->GetTrackId()].color = i->GetColor(); 128 | props[i->GetTrackId()].mode = i->GetMode(); 129 | } 130 | 131 | return props; 132 | } 133 | 134 | void TrackSelectionState::Update() 135 | { 136 | m_continue_button.Update(MouseInfo(Mouse())); 137 | m_back_button.Update(MouseInfo(Mouse())); 138 | 139 | if (IsKeyPressed(KeyEscape) || m_back_button.hit) 140 | { 141 | if (m_state.midi_out) m_state.midi_out->Reset(); 142 | m_state.track_properties = BuildTrackProperties(); 143 | ChangeState(new TitleState(m_state)); 144 | return; 145 | } 146 | 147 | if (IsKeyPressed(KeyEnter) || m_continue_button.hit) 148 | { 149 | 150 | if (m_state.midi_out) m_state.midi_out->Reset(); 151 | m_state.track_properties = BuildTrackProperties(); 152 | ChangeState(new PlayingState(m_state)); 153 | 154 | return; 155 | } 156 | 157 | if (IsKeyPressed(KeyDown) || IsKeyPressed(KeyRight)) 158 | { 159 | m_current_page++; 160 | if (m_current_page == m_page_count) m_current_page = 0; 161 | } 162 | 163 | if (IsKeyPressed(KeyUp) || IsKeyPressed(KeyLeft)) 164 | { 165 | m_current_page--; 166 | if (m_current_page < 0) m_current_page += m_page_count; 167 | } 168 | 169 | m_tooltip = L""; 170 | 171 | if (m_back_button.hovering) m_tooltip = L"Click to return to the title screen."; 172 | if (m_continue_button.hovering) m_tooltip = L"Click to begin playing with these settings."; 173 | 174 | // Our delta milliseconds on the first frame after we seek down to the 175 | // first note is extra long because the seek takes a while. By skipping 176 | // the "Play" that update, we don't have an artificially fast-forwarded 177 | // start. 178 | if (!m_first_update_after_seek) 179 | { 180 | PlayTrackPreview(static_cast(GetDeltaMilliseconds()) * 1000); 181 | } 182 | m_first_update_after_seek = false; 183 | 184 | // Do hit testing on each tile button on this page 185 | size_t start = m_current_page * m_tiles_per_page; 186 | size_t end = std::min( static_cast((m_current_page+1) * m_tiles_per_page), m_track_tiles.size() ); 187 | for (size_t i = start; i < end; ++i) 188 | { 189 | TrackTile &t = m_track_tiles[i]; 190 | 191 | MouseInfo mouse = MouseInfo(Mouse()); 192 | mouse.x -= t.GetX(); 193 | mouse.y -= t.GetY(); 194 | 195 | t.Update(mouse); 196 | 197 | if (t.ButtonLeft().hovering || t.ButtonRight().hovering) 198 | { 199 | switch (t.GetMode()) 200 | { 201 | case Track::ModeNotPlayed: m_tooltip = L"Track won't be played or shown during the game."; break; 202 | case Track::ModePlayedAutomatically: m_tooltip = L"Track will be played automatically by the game."; break; 203 | case Track::ModePlayedButHidden: m_tooltip = L"Track will be played automatically by the game, but also hidden from view."; break; 204 | case Track::ModeYouPlay: m_tooltip = L"'You Play' means you want to play this track yourself."; break; 205 | } 206 | } 207 | 208 | if (t.ButtonPreview().hovering) 209 | { 210 | if (t.IsPreviewOn()) m_tooltip = L"Turn track preview off."; 211 | else m_tooltip = L"Preview how this track sounds."; 212 | } 213 | 214 | if (t.ButtonColor().hovering) m_tooltip = L"Pick a color for this track's notes."; 215 | 216 | if (t.HitPreviewButton()) 217 | { 218 | if (m_state.midi_out) m_state.midi_out->Reset(); 219 | 220 | if (t.IsPreviewOn()) 221 | { 222 | // Turn off any other preview modes 223 | for (size_t j = 0; j < m_track_tiles.size(); ++j) 224 | { 225 | if (i == j) continue; 226 | m_track_tiles[j].TurnOffPreview(); 227 | } 228 | 229 | const microseconds_t PreviewLeadIn = 25000; 230 | const microseconds_t PreviewLeadOut = 25000; 231 | 232 | m_preview_on = true; 233 | m_preview_track_id = t.GetTrackId(); 234 | m_state.midi->Reset(PreviewLeadIn, PreviewLeadOut); 235 | PlayTrackPreview(0); 236 | 237 | // Find the first note in this track so we can skip right to the good part. 238 | microseconds_t additional_time = -PreviewLeadIn; 239 | const MidiTrack &track = m_state.midi->Tracks()[m_preview_track_id]; 240 | for (size_t i = 0; i < track.Events().size(); ++i) 241 | { 242 | const MidiEvent &ev = track.Events()[i]; 243 | if (ev.Type() == MidiEventType_NoteOn && ev.NoteVelocity() > 0) 244 | { 245 | additional_time += track.EventUsecs()[i] - m_state.midi->GetDeadAirStartOffsetMicroseconds() - 1; 246 | break; 247 | } 248 | } 249 | 250 | PlayTrackPreview(additional_time); 251 | m_first_update_after_seek = true; 252 | } 253 | else 254 | { 255 | m_preview_on = false; 256 | } 257 | } 258 | } 259 | 260 | 261 | 262 | 263 | } 264 | 265 | void TrackSelectionState::PlayTrackPreview(microseconds_t delta_microseconds) 266 | { 267 | if (!m_preview_on) return; 268 | 269 | MidiEventListWithTrackId evs = m_state.midi->Update(delta_microseconds); 270 | 271 | for (MidiEventListWithTrackId::const_iterator i = evs.begin(); i != evs.end(); ++i) 272 | { 273 | const MidiEvent &ev = i->second; 274 | if (i->first != m_preview_track_id) continue; 275 | 276 | if (m_state.midi_out) m_state.midi_out->Write(ev); 277 | } 278 | } 279 | 280 | void TrackSelectionState::Draw(Renderer &renderer) const 281 | { 282 | Layout::DrawTitle(renderer, L"Choose Tracks To Play"); 283 | 284 | Layout::DrawHorizontalRule(renderer, GetStateWidth(), Layout::ScreenMarginY); 285 | Layout::DrawHorizontalRule(renderer, GetStateWidth(), GetStateHeight() - Layout::ScreenMarginY); 286 | 287 | Layout::DrawButton(renderer, m_continue_button, GetTexture(ButtonPlaySong)); 288 | Layout::DrawButton(renderer, m_back_button, GetTexture(ButtonBackToTitle)); 289 | 290 | // Write our page count on the screen 291 | TextWriter pagination(GetStateWidth()/2, GetStateHeight() - Layout::SmallFontSize - 30, renderer, true, Layout::ButtonFontSize); 292 | pagination << Text(WSTRING(L"Page " << (m_current_page+1) << L" of " << m_page_count << L" (arrow keys change page)"), Gray); 293 | 294 | TextWriter tooltip(GetStateWidth()/2, GetStateHeight() - Layout::SmallFontSize - 54, renderer, true, Layout::ButtonFontSize); 295 | tooltip << m_tooltip; 296 | 297 | Tga *buttons = GetTexture(InterfaceButtons); 298 | Tga *box = GetTexture(TrackPanel); 299 | 300 | // Draw each track tile on the current page 301 | size_t start = m_current_page * m_tiles_per_page; 302 | size_t end = std::min( static_cast((m_current_page+1) * m_tiles_per_page), m_track_tiles.size() ); 303 | for (size_t i = start; i < end; ++i) 304 | { 305 | m_track_tiles[i].Draw(renderer, m_state.midi, buttons, box); 306 | } 307 | } 308 | --------------------------------------------------------------------------------