Finished implementation of MidiPlayer. Yay!

git-svn-id: file:///srv/svn/repos/haiku/trunk/current@8064 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
mahlzeit
2004-06-19 17:00:40 +00:00
parent 49c6a79a59
commit 79054474dd
4 changed files with 287 additions and 57 deletions
+112 -50
View File
@@ -20,7 +20,6 @@
* DEALINGS IN THE SOFTWARE. * DEALINGS IN THE SOFTWARE.
*/ */
#include <stdio.h>
#include <StorageKit.h> #include <StorageKit.h>
#include "MidiPlayerApp.h" #include "MidiPlayerApp.h"
@@ -42,6 +41,7 @@ MidiPlayerWindow::MidiPlayerWindow()
volume = 75; volume = 75;
windowX = -1; windowX = -1;
windowY = -1; windowY = -1;
threadId = -1;
CreateViews(); CreateViews();
LoadSettings(); LoadSettings();
@@ -52,15 +52,23 @@ MidiPlayerWindow::MidiPlayerWindow()
MidiPlayerWindow::~MidiPlayerWindow() MidiPlayerWindow::~MidiPlayerWindow()
{ {
Stop(false); StopSynth();
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
bool MidiPlayerWindow::QuitRequested() bool MidiPlayerWindow::QuitRequested()
{ {
be_app->PostMessage(B_QUIT_REQUESTED); // There is a race condition when you quit MidiPlayer while we're still
return true; // 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) return ((MidiPlayerWindow*) data)->LoadThread();
{
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();
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
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(); synth.Start();
playButton->SetLabel("Stop");
playing = true; playing = true;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void MidiPlayerWindow::Stop(bool changeButton) void MidiPlayerWindow::StopSynth()
{ {
if (changeButton) if (!synth.IsFinished())
{ {
playButton->SetEnabled(false); synth.Fade();
}
synth.Fade();
if (changeButton)
{
playButton->SetEnabled(true);
playButton->SetLabel("Play");
} }
playing = false; playing = false;
@@ -405,11 +460,15 @@ void MidiPlayerWindow::OnPlayStop()
{ {
if (playing) if (playing)
{ {
Stop(true); threadId = spawn_thread(
_StopThread, "StopThread", B_NORMAL_PRIORITY, this);
resume_thread(threadId);
} }
else else
{ {
Play(); StartSynth();
playButton->SetLabel("Stop");
} }
} }
@@ -419,6 +478,7 @@ void MidiPlayerWindow::OnShowScope()
{ {
scopeEnabled = !scopeEnabled; scopeEnabled = !scopeEnabled;
scopeView->SetEnabled(scopeEnabled); scopeView->SetEnabled(scopeEnabled);
scopeView->Invalidate();
SaveSettings(); SaveSettings();
} }
@@ -444,10 +504,12 @@ void MidiPlayerWindow::OnVolume()
void MidiPlayerWindow::OnDrop(BMessage* msg) void MidiPlayerWindow::OnDrop(BMessage* msg)
{ {
entry_ref ref;
if (msg->FindRef("refs", &ref) == B_OK) if (msg->FindRef("refs", &ref) == B_OK)
{ {
LoadFile(&ref); threadId = spawn_thread(
_LoadThread, "LoadThread", B_NORMAL_PRIORITY, this);
resume_thread(threadId);
} }
} }
+10 -3
View File
@@ -65,9 +65,14 @@ private:
void LoadSettings(); void LoadSettings();
void SaveSettings(); void SaveSettings();
void LoadFile(entry_ref* ref); static int32 _LoadThread(void* data);
void Play(); int32 LoadThread();
void Stop(bool changeButton);
static int32 _StopThread(void* data);
int32 StopThread();
void StartSynth();
void StopSynth();
void OnPlayStop(); void OnPlayStop();
void OnShowScope(); void OnShowScope();
@@ -94,6 +99,8 @@ private:
float windowX; float windowX;
float windowY; float windowY;
BMidiSynthFile synth; BMidiSynthFile synth;
entry_ref ref;
thread_id threadId;
}; };
#endif // MIDI_PLAYER_WINDOW_H #endif // MIDI_PLAYER_WINDOW_H
+147 -2
View File
@@ -20,23 +20,77 @@
* DEALINGS IN THE SOFTWARE. * DEALINGS IN THE SOFTWARE.
*/ */
#include <Synth.h>
#include <Window.h>
#include "ScopeView.h" #include "ScopeView.h"
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
ScopeView::ScopeView() ScopeView::ScopeView()
: BView(BRect(0, 0, 127, 47), NULL, B_FOLLOW_LEFT | B_FOLLOW_TOP, : BView(BRect(0, 0, 127, 63), NULL, B_FOLLOW_LEFT | B_FOLLOW_TOP,
B_WILL_DRAW | B_PULSE_NEEDED) B_WILL_DRAW)
{ {
SetViewColor(0, 0, 0); SetViewColor(0, 0, 0);
enabled = true; enabled = true;
haveFile = false; haveFile = false;
sampleCount = (int32) Bounds().Width();
leftSamples = new int16[sampleCount];
rightSamples = new int16[sampleCount];
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
ScopeView::~ScopeView() 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;
}
}
}
//------------------------------------------------------------------------------
+16
View File
@@ -32,6 +32,10 @@ public:
ScopeView(); ScopeView();
virtual ~ScopeView(); virtual ~ScopeView();
virtual void AttachedToWindow();
virtual void DetachedFromWindow();
virtual void Draw(BRect updateRect);
void SetEnabled(bool flag); void SetEnabled(bool flag);
void SetHaveFile(bool flag); void SetHaveFile(bool flag);
@@ -39,8 +43,20 @@ private:
typedef BView super; typedef BView super;
static int32 _Thread(void* data);
int32 Thread();
void DrawNoFile();
void DrawDisabled();
void DrawPlaying();
bool finished;
bool enabled; bool enabled;
bool haveFile; bool haveFile;
int32 sampleCount;
int16* leftSamples;
int16* rightSamples;
thread_id threadId;
}; };
#endif // SCOPE_VIEW_H #endif // SCOPE_VIEW_H