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:
@@ -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,7 +41,8 @@ MidiPlayerWindow::MidiPlayerWindow()
|
|||||||
volume = 75;
|
volume = 75;
|
||||||
windowX = -1;
|
windowX = -1;
|
||||||
windowY = -1;
|
windowY = -1;
|
||||||
|
threadId = -1;
|
||||||
|
|
||||||
CreateViews();
|
CreateViews();
|
||||||
LoadSettings();
|
LoadSettings();
|
||||||
InitControls();
|
InitControls();
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,10 +65,15 @@ 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();
|
||||||
void OnReverb(reverb_mode mode);
|
void OnReverb(reverb_mode mode);
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -32,15 +32,31 @@ 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);
|
||||||
|
|
||||||
private:
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user