From 79054474ddc6936367324f71176ed8d4bb0e4fe6 Mon Sep 17 00:00:00 2001 From: mahlzeit Date: Sat, 19 Jun 2004 17:00:40 +0000 Subject: [PATCH] Finished implementation of MidiPlayer. Yay! git-svn-id: file:///srv/svn/repos/haiku/trunk/current@8064 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/apps/midiplayer/MidiPlayerWindow.cpp | 164 ++++++++++++++++------- src/apps/midiplayer/MidiPlayerWindow.h | 15 ++- src/apps/midiplayer/ScopeView.cpp | 149 +++++++++++++++++++- src/apps/midiplayer/ScopeView.h | 16 +++ 4 files changed, 287 insertions(+), 57 deletions(-) diff --git a/src/apps/midiplayer/MidiPlayerWindow.cpp b/src/apps/midiplayer/MidiPlayerWindow.cpp index b96059f62f..de56623c26 100644 --- a/src/apps/midiplayer/MidiPlayerWindow.cpp +++ b/src/apps/midiplayer/MidiPlayerWindow.cpp @@ -20,7 +20,6 @@ * DEALINGS IN THE SOFTWARE. */ -#include #include #include "MidiPlayerApp.h" @@ -42,7 +41,8 @@ MidiPlayerWindow::MidiPlayerWindow() volume = 75; windowX = -1; windowY = -1; - + threadId = -1; + CreateViews(); LoadSettings(); InitControls(); @@ -52,15 +52,23 @@ MidiPlayerWindow::MidiPlayerWindow() MidiPlayerWindow::~MidiPlayerWindow() { - Stop(false); + StopSynth(); } //------------------------------------------------------------------------------ bool MidiPlayerWindow::QuitRequested() { - be_app->PostMessage(B_QUIT_REQUESTED); - return true; + // There is a race condition when you quit MidiPlayer while we're still + // fading out, because fading happens in a separate thread. In this odd + // case, we simply won't let the user quit the app :-) + + if (threadId == -1) + { + be_app->PostMessage(B_QUIT_REQUESTED); + return true; + } + return false; } //------------------------------------------------------------------------------ @@ -338,62 +346,109 @@ void MidiPlayerWindow::SaveSettings() //------------------------------------------------------------------------------ -void MidiPlayerWindow::LoadFile(entry_ref* ref) +int32 MidiPlayerWindow::_LoadThread(void* data) { - if (playing) - { - Stop(false); - } - - synth.UnloadFile(); - - if (synth.LoadFile(ref) == B_OK) - { - // Ideally, we would call SetVolume() in InitControls(), - // but for some reason that doesn't work; BMidiSynthFile - // will use the default volume instead. So we do it here. - synth.SetVolume(volume / 100.0f); - - playButton->SetEnabled(true); - scopeView->SetHaveFile(true); - } - else - { - (new BAlert( - NULL, "Could not load song", "Okay", NULL, NULL, - B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go(); - - playButton->SetEnabled(false); - scopeView->SetHaveFile(false); - } - - Play(); + return ((MidiPlayerWindow*) data)->LoadThread(); } //------------------------------------------------------------------------------ -void MidiPlayerWindow::Play() +int32 MidiPlayerWindow::LoadThread() { + // We do this in a separate fire-and-forget thread, just in case a song + // was already playing. The call to StopSynth() will block, and we need + // to keep the window's looper free for repainting the ScopeView during + // the fade out. The R5 MidiPlayer does that too and it looks neat. + + if (playing) + { + StopSynth(); + } + + synth.UnloadFile(); + + if (synth.LoadFile(&ref) == B_OK) + { + // Ideally, we would call SetVolume() in InitControls(), + // but for some reason that doesn't work: BMidiSynthFile + // will use the default volume instead. So we do it here. + synth.SetVolume(volume / 100.0f); + + Lock(); + playButton->SetEnabled(true); + playButton->SetLabel("Stop"); + scopeView->SetHaveFile(true); + scopeView->Invalidate(); + Unlock(); + + StartSynth(); + } + else + { + Lock(); + playButton->SetEnabled(false); + playButton->SetLabel("Play"); + scopeView->SetHaveFile(false); + scopeView->Invalidate(); + Unlock(); + + (new BAlert( + NULL, "Could not load song", "Okay", NULL, NULL, + B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go(); + } + + threadId = -1; + return 0; +} + +//------------------------------------------------------------------------------ + +int32 MidiPlayerWindow::_StopThread(void* data) +{ + return ((MidiPlayerWindow*) data)->StopThread(); +} + +//------------------------------------------------------------------------------ + +int32 MidiPlayerWindow::StopThread() +{ + Lock(); + playButton->SetEnabled(false); + Unlock(); + + StopSynth(); + + Lock(); + playButton->SetEnabled(true); + playButton->SetLabel("Play"); + Unlock(); + + threadId = -1; + return 0; +} + +//------------------------------------------------------------------------------ + +void MidiPlayerWindow::StartSynth() +{ + // When playback of the song ends, we don't automatically go back into + // "stopped" mode. It is possible to do this with synth.SetFileHook(), + // but that made the code kinda messy (with all the threads and stuff). + // Note: SetFileHook(NULL) crashes the softsynth. In any case, should + // we ever add this in, remember to call SetFileHook() *after* Start() + // or it won't work. + synth.Start(); - playButton->SetLabel("Stop"); playing = true; } //------------------------------------------------------------------------------ -void MidiPlayerWindow::Stop(bool changeButton) +void MidiPlayerWindow::StopSynth() { - if (changeButton) + if (!synth.IsFinished()) { - playButton->SetEnabled(false); - } - - synth.Fade(); - - if (changeButton) - { - playButton->SetEnabled(true); - playButton->SetLabel("Play"); + synth.Fade(); } playing = false; @@ -405,11 +460,15 @@ void MidiPlayerWindow::OnPlayStop() { if (playing) { - Stop(true); + threadId = spawn_thread( + _StopThread, "StopThread", B_NORMAL_PRIORITY, this); + + resume_thread(threadId); } else { - Play(); + StartSynth(); + playButton->SetLabel("Stop"); } } @@ -419,6 +478,7 @@ void MidiPlayerWindow::OnShowScope() { scopeEnabled = !scopeEnabled; scopeView->SetEnabled(scopeEnabled); + scopeView->Invalidate(); SaveSettings(); } @@ -444,10 +504,12 @@ void MidiPlayerWindow::OnVolume() void MidiPlayerWindow::OnDrop(BMessage* msg) { - entry_ref ref; if (msg->FindRef("refs", &ref) == B_OK) { - LoadFile(&ref); + threadId = spawn_thread( + _LoadThread, "LoadThread", B_NORMAL_PRIORITY, this); + + resume_thread(threadId); } } diff --git a/src/apps/midiplayer/MidiPlayerWindow.h b/src/apps/midiplayer/MidiPlayerWindow.h index 7c9e938b23..92a1c8d834 100644 --- a/src/apps/midiplayer/MidiPlayerWindow.h +++ b/src/apps/midiplayer/MidiPlayerWindow.h @@ -65,10 +65,15 @@ private: void LoadSettings(); void SaveSettings(); - void LoadFile(entry_ref* ref); - void Play(); - void Stop(bool changeButton); - + static int32 _LoadThread(void* data); + int32 LoadThread(); + + static int32 _StopThread(void* data); + int32 StopThread(); + + void StartSynth(); + void StopSynth(); + void OnPlayStop(); void OnShowScope(); void OnReverb(reverb_mode mode); @@ -94,6 +99,8 @@ private: float windowX; float windowY; BMidiSynthFile synth; + entry_ref ref; + thread_id threadId; }; #endif // MIDI_PLAYER_WINDOW_H diff --git a/src/apps/midiplayer/ScopeView.cpp b/src/apps/midiplayer/ScopeView.cpp index 89a5eb59a1..a186dc80fc 100644 --- a/src/apps/midiplayer/ScopeView.cpp +++ b/src/apps/midiplayer/ScopeView.cpp @@ -20,23 +20,77 @@ * DEALINGS IN THE SOFTWARE. */ +#include +#include + #include "ScopeView.h" //------------------------------------------------------------------------------ ScopeView::ScopeView() - : BView(BRect(0, 0, 127, 47), NULL, B_FOLLOW_LEFT | B_FOLLOW_TOP, - B_WILL_DRAW | B_PULSE_NEEDED) + : BView(BRect(0, 0, 127, 63), NULL, B_FOLLOW_LEFT | B_FOLLOW_TOP, + B_WILL_DRAW) { SetViewColor(0, 0, 0); + enabled = true; haveFile = false; + + sampleCount = (int32) Bounds().Width(); + leftSamples = new int16[sampleCount]; + rightSamples = new int16[sampleCount]; } //------------------------------------------------------------------------------ ScopeView::~ScopeView() { + delete[] leftSamples; + delete[] rightSamples; +} + +//------------------------------------------------------------------------------ + +void ScopeView::AttachedToWindow() +{ + finished = false; + threadId = spawn_thread(_Thread, "ScopeThread", B_NORMAL_PRIORITY, this); + if (threadId >= B_OK) + { + resume_thread(threadId); + } +} + +//------------------------------------------------------------------------------ + +void ScopeView::DetachedFromWindow() +{ + if (threadId >= B_OK) + { + finished = true; + status_t exitValue; + wait_for_thread(threadId, &exitValue); + } +} + +//------------------------------------------------------------------------------ + +void ScopeView::Draw(BRect updateRect) +{ + super::Draw(updateRect); + + if (!haveFile) + { + DrawNoFile(); + } + else if (!enabled) + { + DrawDisabled(); + } + else + { + DrawPlaying(); + } } //------------------------------------------------------------------------------ @@ -54,3 +108,94 @@ void ScopeView::SetHaveFile(bool flag) } //------------------------------------------------------------------------------ + +int32 ScopeView::_Thread(void* data) +{ + return ((ScopeView*) data)->Thread(); +} + +//------------------------------------------------------------------------------ + +int32 ScopeView::Thread() +{ + // Because Pulse() was too slow, I created a thread that tells the + // ScopeView to repaint itself. Note that we need to call LockLooper + // with a timeout, otherwise we'll deadlock in DetachedFromWindow(). + + while (!finished) + { + if (enabled && haveFile) + { + if (LockLooperWithTimeout(50000) == B_OK) + { + Invalidate(); + UnlockLooper(); + } + } + snooze(50000); + } + return 0; +} + +//------------------------------------------------------------------------------ + +void ScopeView::DrawNoFile() +{ + const char* string = "Drop MIDI file here"; + + font_height height; + GetFontHeight(&height); + + float strWidth = StringWidth(string); + float strHeight = height.ascent + height.descent; + + float x = (Bounds().Width() - strWidth)/2; + float y = height.ascent + (Bounds().Height() - strHeight)/2; + + SetHighColor(255, 255, 255); + SetLowColor(ViewColor()); + SetDrawingMode(B_OP_OVER); + + DrawString(string, BPoint(x, y)); +} + +//------------------------------------------------------------------------------ + +void ScopeView::DrawDisabled() +{ + SetHighColor(64, 64, 64); + + StrokeLine( + BPoint(0, Bounds().Height() / 2), + BPoint(Bounds().Width(), Bounds().Height() / 2)); +} + +//------------------------------------------------------------------------------ + +void ScopeView::DrawPlaying() +{ + int32 width = (int32) Bounds().Width(); + int32 height = (int32) Bounds().Height(); + + // Scope drawing magic based on code by Michael Pfeiffer. + + int32 size = be_synth->GetAudio(leftSamples, rightSamples, sampleCount); + if (size > 0) + { + SetHighColor(255, 0, 130); + SetLowColor(0, 130, 0); + #define N 16 + int32 x, y, sx = 0, f = (height << N) / 65535, dy = height / 2; + for (int32 i = 0; i < width; i++) + { + x = sx / width; + y = ((leftSamples[x] * f) >> N) + dy; + FillRect(BRect(i, y, i, y)); + y = ((rightSamples[x] * f) >> N) + dy; + FillRect(BRect(i, y, i, y), B_SOLID_LOW); + sx += size; + } + } +} + +//------------------------------------------------------------------------------ diff --git a/src/apps/midiplayer/ScopeView.h b/src/apps/midiplayer/ScopeView.h index 98729be51b..28bf09c461 100644 --- a/src/apps/midiplayer/ScopeView.h +++ b/src/apps/midiplayer/ScopeView.h @@ -32,15 +32,31 @@ public: ScopeView(); virtual ~ScopeView(); + virtual void AttachedToWindow(); + virtual void DetachedFromWindow(); + virtual void Draw(BRect updateRect); + void SetEnabled(bool flag); void SetHaveFile(bool flag); private: typedef BView super; + + static int32 _Thread(void* data); + int32 Thread(); + void DrawNoFile(); + void DrawDisabled(); + void DrawPlaying(); + + bool finished; bool enabled; bool haveFile; + int32 sampleCount; + int16* leftSamples; + int16* rightSamples; + thread_id threadId; }; #endif // SCOPE_VIEW_H