diff --git a/src/apps/midiplayer/Jamfile b/src/apps/midiplayer/Jamfile index 5a271fd690..a9de4cd0fa 100644 --- a/src/apps/midiplayer/Jamfile +++ b/src/apps/midiplayer/Jamfile @@ -13,4 +13,5 @@ App MidiPlayer : LinkSharedOSLibs MidiPlayer : be midi + midi2 ; diff --git a/src/apps/midiplayer/MidiPlayerApp.cpp b/src/apps/midiplayer/MidiPlayerApp.cpp index a138c4a0f4..73e51b1ac6 100644 --- a/src/apps/midiplayer/MidiPlayerApp.cpp +++ b/src/apps/midiplayer/MidiPlayerApp.cpp @@ -47,7 +47,7 @@ void MidiPlayerApp::AboutRequested() { (new BAlert( NULL, - "Haiku MIDI Player\n\n" + "Haiku MIDI Player 1.0.0 beta\n\n" "This tiny program\n" "Knows how to play thousands of\n" "Cheesy sounding songs", diff --git a/src/apps/midiplayer/MidiPlayerWindow.cpp b/src/apps/midiplayer/MidiPlayerWindow.cpp index 28cd481c97..fab3354f3a 100644 --- a/src/apps/midiplayer/MidiPlayerWindow.cpp +++ b/src/apps/midiplayer/MidiPlayerWindow.cpp @@ -20,6 +20,7 @@ * DEALINGS IN THE SOFTWARE. */ +#include #include #include "MidiPlayerApp.h" @@ -42,6 +43,7 @@ MidiPlayerWindow::MidiPlayerWindow() windowX = -1; windowY = -1; threadId = -1; + inputId = -1; be_synth->SetSamplingRate(44100); @@ -86,6 +88,10 @@ void MidiPlayerWindow::MessageReceived(BMessage* msg) case MSG_SHOW_SCOPE: OnShowScope(); break; + + case MSG_INPUT_CHANGED: + OnInputChanged(msg); + break; case MSG_REVERB_NONE: OnReverb(B_REVERB_NONE); @@ -137,9 +143,77 @@ void MidiPlayerWindow::FrameMoved(BPoint origin) //------------------------------------------------------------------------------ +void MidiPlayerWindow::MenusBeginning() +{ + for (int32 t = inputPopUp->CountItems() - 1; t > 0; --t) + { + delete inputPopUp->RemoveItem(t); + } + + bool seenInput = false; + + if (inputId == -1) + { + inputOff->SetMarked(true); + seenInput = true; + } + + int32 id = 0; + BMidiEndpoint* endp; + while ((endp = BMidiRoster::NextEndpoint(&id)) != NULL) + { + if (endp->IsProducer()) + { + BMessage* msg = new BMessage; + msg->what = MSG_INPUT_CHANGED; + msg->AddInt32("id", id); + + BMenuItem* item = new BMenuItem(endp->Name(), msg); + inputPopUp->AddItem(item); + + if (inputId == id) + { + item->SetMarked(true); + seenInput = true; + } + } + + endp->Release(); + } + + if (!seenInput) // endpoint no longer exists + { + inputOff->SetMarked(true); + } +} + +//------------------------------------------------------------------------------ + +void MidiPlayerWindow::CreateInputMenu() +{ + inputPopUp = new BPopUpMenu("inputPopUp"); + + BMessage* msg = new BMessage; + msg->what = MSG_INPUT_CHANGED; + msg->AddInt32("id", -1); + + inputOff = new BMenuItem("Off", msg); + + inputPopUp->AddItem(inputOff); + + inputMenu = new BMenuField( + BRect(0, 0, 128, 17), "inputMenu", "Live Input:", inputPopUp, + B_FOLLOW_LEFT | B_FOLLOW_TOP); + + inputMenu->SetDivider(55); + inputMenu->ResizeToPreferred(); +} + +//------------------------------------------------------------------------------ + void MidiPlayerWindow::CreateReverbMenu() { - BPopUpMenu* menu = new BPopUpMenu("reverbPopUp"); + BPopUpMenu* reverbPopUp = new BPopUpMenu("reverbPopUp"); reverbNone = new BMenuItem( "None", new BMessage(MSG_REVERB_NONE)); @@ -159,18 +233,18 @@ void MidiPlayerWindow::CreateReverbMenu() reverbDungeon = new BMenuItem( "Dungeon", new BMessage(MSG_REVERB_DUNGEON)); - menu->AddItem(reverbNone); - menu->AddItem(reverbCloset); - menu->AddItem(reverbGarage); - menu->AddItem(reverbIgor); - menu->AddItem(reverbCavern); - menu->AddItem(reverbDungeon); + reverbPopUp->AddItem(reverbNone); + reverbPopUp->AddItem(reverbCloset); + reverbPopUp->AddItem(reverbGarage); + reverbPopUp->AddItem(reverbIgor); + reverbPopUp->AddItem(reverbCavern); + reverbPopUp->AddItem(reverbDungeon); reverbMenu = new BMenuField( - BRect(0, 0, 128, 17), "reverbMenu", "Reverb:", menu, + BRect(0, 0, 128, 17), "reverbMenu", "Reverb:", reverbPopUp, B_FOLLOW_LEFT | B_FOLLOW_TOP); - reverbMenu->SetDivider(50); + reverbMenu->SetDivider(55); reverbMenu->ResizeToPreferred(); } @@ -187,6 +261,7 @@ void MidiPlayerWindow::CreateViews() showScope->SetValue(B_CONTROL_ON); showScope->ResizeToPreferred(); + CreateInputMenu(); CreateReverbMenu(); volumeSlider = new BSlider( @@ -197,7 +272,7 @@ void MidiPlayerWindow::CreateViews() volumeSlider->UseFillColor(true, &col); volumeSlider->SetModificationMessage(new BMessage(MSG_VOLUME)); volumeSlider->ResizeToPreferred(); - volumeSlider->ResizeTo(_W(scopeView) - 37, _H(volumeSlider)); + volumeSlider->ResizeTo(_W(scopeView) - 42, _H(volumeSlider)); playButton = new BButton( BRect(0, 1, 80, 1), "playButton", "Play", new BMessage(MSG_PLAY_STOP), @@ -226,13 +301,14 @@ void MidiPlayerWindow::CreateViews() float width = 8 + _W(scopeView) + 8; float height = - + 8 + _H(scopeView) + 8 + _H(scopeView) + 8 + _H(showScope) - + 4 + _H(reverbMenu) + + 4 + _H(inputMenu) + + _H(reverbMenu) + 2 + _H(volumeSlider) + 10 + _H(divider) + 6 + _H(playButton) - + 10; + + 16; ResizeTo(width, height); @@ -241,6 +317,7 @@ void MidiPlayerWindow::CreateViews() background->AddChild(scopeView); background->AddChild(showScope); background->AddChild(reverbMenu); + background->AddChild(inputMenu); background->AddChild(volumeLabel); background->AddChild(volumeSlider); background->AddChild(divider); @@ -250,14 +327,17 @@ void MidiPlayerWindow::CreateViews() scopeView->MoveTo(8, y); y += _H(scopeView) + 8; - showScope->MoveTo(8 + 50, y); + showScope->MoveTo(8 + 55, y); y += _H(showScope) + 4; + inputMenu->MoveTo(8, y); + + y += _H(inputMenu); reverbMenu->MoveTo(8, y); y += _H(reverbMenu) + 2; volumeLabel->MoveTo(8, y); - volumeSlider->MoveTo(8 + 44, y); + volumeSlider->MoveTo(8 + 49, y); y += _H(volumeSlider) + 10; divider->MoveTo(8, y); @@ -287,6 +367,8 @@ void MidiPlayerWindow::InitControls() showScope->SetValue(scopeEnabled ? B_CONTROL_ON : B_CONTROL_OFF); scopeView->SetEnabled(scopeEnabled); + inputOff->SetMarked(true); + reverbNone->SetMarked(reverb == B_REVERB_NONE); reverbCloset->SetMarked(reverb == B_REVERB_CLOSET); reverbGarage->SetMarked(reverb == B_REVERB_GARAGE); @@ -486,6 +568,29 @@ void MidiPlayerWindow::OnShowScope() //------------------------------------------------------------------------------ +void MidiPlayerWindow::OnInputChanged(BMessage* msg) +{ + int32 newId; + if (msg->FindInt32("id", &newId) == B_OK) + { + inputId = newId; + + // if (!instrLoaded) + // be_synth->LoadInstruments(all) + + // if id != -1 + // if playing -> Stop() // AARGH, need thread! + // connect SynthBridge (from MidiUtil) to producer endpoint + // else if SynthBridge still connected + // disconnect SynthBridge + + // NOTE: we should also disconnect SynthBridge when launching + // a MIDI file!!! (successfull or not) + } +} + +//------------------------------------------------------------------------------ + void MidiPlayerWindow::OnReverb(reverb_mode mode) { reverb = mode; diff --git a/src/apps/midiplayer/MidiPlayerWindow.h b/src/apps/midiplayer/MidiPlayerWindow.h index 92a1c8d834..358161512e 100644 --- a/src/apps/midiplayer/MidiPlayerWindow.h +++ b/src/apps/midiplayer/MidiPlayerWindow.h @@ -32,6 +32,7 @@ enum { MSG_PLAY_STOP = 1000, MSG_SHOW_SCOPE, + MSG_INPUT_CHANGED, MSG_REVERB_NONE, MSG_REVERB_CLOSET, MSG_REVERB_GARAGE, @@ -53,11 +54,13 @@ public: virtual bool QuitRequested(); virtual void MessageReceived(BMessage* msg); virtual void FrameMoved(BPoint origin); + virtual void MenusBeginning(); private: typedef BWindow super; + void CreateInputMenu(); void CreateReverbMenu(); void CreateViews(); void CenterOnScreen(); @@ -76,12 +79,16 @@ private: void OnPlayStop(); void OnShowScope(); + void OnInputChanged(BMessage* msg); void OnReverb(reverb_mode mode); void OnVolume(); void OnDrop(BMessage* msg); ScopeView* scopeView; BCheckBox* showScope; + BMenuField* inputMenu; + BPopUpMenu* inputPopUp; + BMenuItem* inputOff; BMenuField* reverbMenu; BMenuItem* reverbNone; BMenuItem* reverbCloset; @@ -101,6 +108,7 @@ private: BMidiSynthFile synth; entry_ref ref; thread_id threadId; + int32 inputId; }; #endif // MIDI_PLAYER_WINDOW_H diff --git a/src/apps/midiplayer/readme.html b/src/apps/midiplayer/readme.html index 393980289b..cb5b302eda 100644 --- a/src/apps/midiplayer/readme.html +++ b/src/apps/midiplayer/readme.html @@ -4,23 +4,37 @@ -

Haiku MIDI Player

+

Haiku MIDI Player 1.0.0 beta

This is a simple application to play MIDI songs.

-

Differences with the BeOS R5 MidiPlayer:

+

To play a MIDI song, you can:

+ + + +

Hopefully, most of the options will be obvious, although Live Input may require a few lines of explanation.

+ +

The Live Input feature enables you to attach other applications to the Haiku software synthesizer, for example a virtual MIDI keyboard. MidiPlayer looks at the MIDI roster for compatible producer endpoints. If none are found, Live Input is not available.

+ +

Differences with the BeOS R5 MidiPlayer:

If people really really want these missing features, then feel free to add them :-)

-

Note: The code can be a little tricky at times, since it uses fire-and-forget threads to load new songs and to stop playback. The reason for this is the scope: we want to keep repainting the scope view even when fading out a song (because this looks cool), but fading blocks the calling thread. If we were to block the window's looper, then the scope wouldn't repaint during the fade-out. So we fade using a separate thread instead. Unfortunately, this makes the code much less clean as it introduces several race conditions. Yech. See the source for more details.

+

Implementation notes

+ +

The code can be a little tricky at times, since it uses fire-and-forget threads to load new songs and to stop playback. The reason for this is the scope: we want to keep repainting the ScopeView even when fading out a song (because this looks cool), but fading blocks the calling thread. If we were to block the window's looper, then the scope wouldn't repaint during the fade-out. So we fade using a separate thread instead. Unfortunately, this makes the code much less clean as it introduces several race conditions. Yech. See the source for more details.