|
From: Lasse Kärkkäi. <tr...@us...> - 2010-01-19 06:36:04
|
Module: performous
Branch: master
Commit: a26eb2f5ed4d2d28c2f75fa6c41b5009f159ddb9
Author: Lasse Karkkainen <tro...@tr...>
Date: Tue Jan 19 08:33:09 2010 +0200
Fixed MIDI drums and added autodetection (not tested).
PortMidi wrapper improved and added workarounds for buggy operation of PortMidi.
Program initialization order changed so that graphics is initialized before other parts.
Hacked a "Loading..." indicator and "FATAL ERROR" displayer using flash messages.
Disabled flash message fade-in to make those messages appear, but now the messages don't go away anymore :(
---
data/schema.xml | 12 ++++++------
game/joystick.cc | 14 +++++++++-----
game/joystick.hh | 8 ++++----
game/main.cc | 27 +++++++++++++++++++++++----
game/portmidi.hh | 32 +++++++++++++++++++++++++++++++-
game/screenmanager.cc | 2 +-
6 files changed, 74 insertions(+), 21 deletions(-)
diff --git a/data/schema.xml b/data/schema.xml
index 58cd9c4..8f3e598 100644
--- a/data/schema.xml
+++ b/data/schema.xml
@@ -78,12 +78,6 @@ to save the current settings to XML.
<long>Enable keyboard as dance pad.</long>
</locale>
</entry>
- <entry name="game/midi_drums" type="string">
- <locale name="C">
- <short>MIDI drum mapping</short>
- <long>Mapping of hardware MIDI device: devID:kick,pad1,pad2,pad3,pad4</long>
- </locale>
- </entry>
<entry name="game/instruments" type="string_list">
<!-- Should be SDL_ID:{GUITAR_GUITARHERO|GUITAR_GUITARHERO_XPLORER|GUITAR_ROCKBAND_PS3|GUITAR_ROCKBAND_XB360|DRUMS_GUITARHERO|DRUMS_ROCKBAND_PS3|DRUMS_ROCKBAND_XB360|DRUMS_MIDI|DANCEPAD_GENERIC|DANCEPAD_TIGERGAME}
example:
@@ -223,6 +217,12 @@ to save the current settings to XML.
</entry>
<!-- System preferences -->
+ <entry name="system/midi_input" type="string" value="">
+ <locale name="C">
+ <short>Hardware MIDI input device</short>
+ <long>Part of sound card name or its number or empty to use the first available device. Used currently for MIDI drum controllers. Empty to select the first available device.</long>
+ </locale>
+ </entry>
<entry name="system/path_songs" type="string_list">
<stringvalue>DATADIR/songs/</stringvalue>
<stringvalue>/usr/local/share/games/ultrastar/songs/</stringvalue>
diff --git a/game/joystick.cc b/game/joystick.cc
index 2d17109..588ac4c 100644
--- a/game/joystick.cc
+++ b/game/joystick.cc
@@ -4,28 +4,32 @@
#include <boost/lexical_cast.hpp>
#ifdef USE_PORTMIDI
-input::MidiDrums::MidiDrums(int devId): stream(devId), devnum(0x8000 + devId) {
+input::MidiDrums::MidiDrums(): stream(pm::findDevice(true, config["system/midi_input"].s())), devnum(0x8000) {
+ while (detail::devices.find(devnum) != detail::devices.end()) ++devnum;
detail::devices[devnum] = detail::InputDevPrivate(detail::DRUMS_MIDI);
event.type = Event::PRESS;
for (unsigned int i = 0; i < BUTTONS; ++i) event.pressed[i] = false;
map[35] = map[36] = 0; // Bass drum 1/2
map[38] = map[40] = 1; // Snare 1/2
map[42] = map[46] = 2; // Hi-hat closed/open
+ map[52] = map[57] = 2; // Crash2 1/2
map[41] = map[43] = 3; // Tom low 1/2
map[45] = map[47] = 3; // Tom mid 1/2
map[48] = map[50] = 3; // Tom high 1/2
map[49] = map[51] = 4; // Cymbal crash/ride
}
+#include <iomanip>
+
void input::MidiDrums::process() {
PmEvent ev;
while (Pm_Read(stream, &ev, 1) == 1) {
- if ((ev.message & 0xFF) != 0x99) continue;
- unsigned char ch = ev.message >> 8;
+ if ((ev.message & 0xFF) != 0x99) continue; // 0x99 = channel 10 (percussion) note ON
+ unsigned char note = ev.message >> 8;
//unsigned char vel = ev.message >> 16;
- Map::const_iterator it = map.find(ch);
+ Map::const_iterator it = map.find(note);
if (it == map.end()) {
- std::cout << "Unassigned MIDI drum event: channel " << ch << std::endl;
+ std::cout << "Unassigned MIDI drum event: note " << note << std::endl;
continue;
}
event.button = it->second;
diff --git a/game/joystick.hh b/game/joystick.hh
index e28bf97..5d43daf 100644
--- a/game/joystick.hh
+++ b/game/joystick.hh
@@ -155,10 +155,10 @@ namespace input {
#ifdef USE_PORTMIDI
class MidiDrums {
- public:
- MidiDrums(int devId);
+ public:
+ MidiDrums();
void process();
- private:
+ private:
pm::Input stream;
unsigned int devnum;
Event event;
@@ -167,5 +167,5 @@ namespace input {
};
#endif
-};
+}
diff --git a/game/main.cc b/game/main.cc
index 4843252..f23bc06 100644
--- a/game/main.cc
+++ b/game/main.cc
@@ -135,15 +135,22 @@ void audioSetup(Capture& capture, Audio& audio) {
}
void mainLoop(std::string const& songlist) {
+ Window window(config["graphic/window_width"].i(), config["graphic/window_height"].i(), config["graphic/fullscreen"].b());
+ ScreenManager sm;
try {
+ sm.FlashMessage(_("Loading..."));
+ window.blank();
+ sm.FlashMessages();
+ window.swap();
Capture capture;
Audio audio;
audioSetup(capture, audio);
Backgrounds backgrounds;
Database database(getConfigDir() / "database.xml");
Songs songs(database, songlist);
- ScreenManager sm;
- Window window(config["graphic/window_width"].i(), config["graphic/window_height"].i(), config["graphic/fullscreen"].b());
+ boost::scoped_ptr<input::MidiDrums> midiDrums;
+ // TODO: Proper error handling...
+ try { midiDrums.reset(new input::MidiDrums); } catch (std::runtime_error&) {}
sm.addScreen(new ScreenIntro("Intro", audio, capture));
sm.addScreen(new ScreenSongs("Songs", audio, songs, database));
sm.addScreen(new ScreenSing("Sing", audio, capture, database, backgrounds));
@@ -155,6 +162,7 @@ void mainLoop(std::string const& songlist) {
// Main loop
boost::xtime time = now();
unsigned frames = 0;
+ sm.FlashMessage("");
while (!sm.isFinished()) {
if( g_take_screenshot ) {
fs::path filename;
@@ -188,13 +196,20 @@ void mainLoop(std::string const& songlist) {
frames = 0;
}
// Process events for the next frame
+ if (midiDrums) midiDrums->process();
checkEvents_SDL(sm, window);
} catch (std::runtime_error& e) {
std::cerr << "ERROR: " << e.what() << std::endl;
+ sm.FlashMessage(std::string("ERROR: ") + e.what());
}
}
} catch (std::exception& e) {
- std::cout << "FATAL ERROR: " << e.what() << std::endl;
+ std::cerr << "FATAL ERROR: " << e.what() << std::endl;
+ sm.FlashMessage(std::string("FATAL ERROR: ") + e.what());
+ window.blank();
+ sm.FlashMessages();
+ window.swap();
+ boost::thread::sleep(now() + 2.0);
} catch (QuitNow&) {
std::cout << "Terminated." << std::endl;
}
@@ -246,7 +261,7 @@ template <typename Container> void confOverride(Container const& c, std::string
std::copy(c.begin(), c.end(), std::back_inserter(sl));
}
-int main(int argc, char** argv) {
+int main(int argc, char** argv) try {
#ifdef USE_GETTEXT
// initialize gettext
#ifdef _MSC_VER
@@ -344,6 +359,8 @@ int main(int argc, char** argv) {
std::cout << std::flush;
return 0;
}
+ // Dump a list of MIDI input devices
+ pm::dumpDevices(true);
// Read config files
try {
readConfig();
@@ -365,5 +382,7 @@ int main(int argc, char** argv) {
// Run the game init and main loop
mainLoop(songlist);
return 0; // Do not remove. SDL_Main (which this function is called on some platforms) needs return statement.
+} catch (std::exception& e) {
+ std::cerr << "FATAL ERROR: " << e.what() << std::endl;
}
diff --git a/game/portmidi.hh b/game/portmidi.hh
index 848c0f2..8e8b1d7 100644
--- a/game/portmidi.hh
+++ b/game/portmidi.hh
@@ -2,6 +2,7 @@
#include <portmidi.h>
#include <stdexcept>
+#include <iostream>
namespace pm {
struct Initialize {
@@ -15,12 +16,41 @@ namespace pm {
protected:
PortMidiStream* m_handle;
Stream(): m_handle() {}
+ void abort() { Pm_Abort(m_handle); m_handle = NULL; }
~Stream() { if (m_handle) Pm_Close(m_handle); }
};
-
+
+ namespace {
+ void dumpDevices(bool input) {
+ std::cout << "MIDI devices:" << std::endl;
+ for (int devId = Pm_CountDevices(); devId--;) {
+ PmDeviceInfo const* info = Pm_GetDeviceInfo(devId);
+ if (info->input != input) continue;
+ if (info->opened) continue;
+ std::cout << " " << info->name << std::endl;
+ }
+ }
+ int findDevice(bool input, std::string const& name = "") {
+ // Loop in reverse order because the last devices are more likely good ones
+ for (int devId = Pm_CountDevices(); devId--;) {
+ PmDeviceInfo const* info = Pm_GetDeviceInfo(devId);
+ if (info->input != input) continue;
+ if (info->opened) continue;
+ if (!name.empty() && std::string(info->name).find(name) == std::string::npos) continue;
+ return devId;
+ }
+ throw std::runtime_error("No matching PortMidi device found");
+ }
+ }
+
class Input: public Stream {
public:
Input(int devId) {
+ // Errors must be handled here because otherwise PortMidi will just exit() the program...
+ if (devId < 0 || devId >= Pm_CountDevices()) throw std::runtime_error("Invalid PortMidi device ID");
+ PmDeviceInfo const* info = Pm_GetDeviceInfo(devId);
+ if (!info->input) throw std::runtime_error("The PortMidi device is an output device (input device needed)");
+ if (info->opened) throw std::runtime_error("The PortMidi device is already open");
PmError err = Pm_OpenInput(&m_handle, devId, 0, 1024, 0, 0);
if (err) throw std::runtime_error("Pm_OpenInput failed");
}
diff --git a/game/screenmanager.cc b/game/screenmanager.cc
index b45f1e9..bfa17d9 100644
--- a/game/screenmanager.cc
+++ b/game/screenmanager.cc
@@ -6,7 +6,7 @@
template<> ScreenManager* Singleton<ScreenManager>::ms_Singleton = NULL;
ScreenManager::ScreenManager(): m_finished(false), currentScreen(), m_messagePopup(0.0, 1.0), m_textMessage(getThemePath("message_text.svg")) {
- m_timeToFade = 1.0f;
+ m_timeToFade = 0.0f;
m_timeToShow = 3.0f;
m_messagePopup.setTarget(100.0);
|