haiku/src/apps/midiplayer/MidiPlayerWindow.cpp

540 lines
12 KiB
C++

/*
* Copyright (c) 2004 Matthijs Hollemans
* Copyright (c) 2008-2014 Haiku, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "MidiPlayerWindow.h"
#include <Catalog.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <MidiProducer.h>
#include <MidiRoster.h>
#include <SeparatorView.h>
#include <StorageKit.h>
#include <SpaceLayoutItem.h>
#include "MidiPlayerApp.h"
#include "ScopeView.h"
#include "SynthBridge.h"
#define _W(a) (a->Frame().Width())
#define _H(a) (a->Frame().Height())
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Main Window"
// #pragma mark - MidiPlayerWindow
MidiPlayerWindow::MidiPlayerWindow()
:
BWindow(BRect(0, 0, 1, 1), B_TRANSLATE_SYSTEM_NAME("MidiPlayer"),
B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_NOT_RESIZABLE
| B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS)
{
fIsPlaying = false;
fScopeEnabled = true;
fReverbMode = B_REVERB_BALLROOM;
fVolume = 75;
fWindowX = -1;
fWindowY = -1;
fInputId = -1;
fInstrumentLoaded = false;
be_synth->SetSamplingRate(44100);
fSynthBridge = new SynthBridge;
//fSynthBridge->Register();
CreateViews();
LoadSettings();
InitControls();
}
MidiPlayerWindow::~MidiPlayerWindow()
{
StopSynth();
//fSynthBridge->Unregister();
fSynthBridge->Release();
}
bool
MidiPlayerWindow::QuitRequested()
{
be_app->PostMessage(B_QUIT_REQUESTED);
return true;
}
void
MidiPlayerWindow::MessageReceived(BMessage* message)
{
switch (message->what) {
case MSG_PLAY_STOP:
OnPlayStop();
break;
case MSG_SHOW_SCOPE:
OnShowScope();
break;
case MSG_INPUT_CHANGED:
OnInputChanged(message);
break;
case MSG_REVERB_NONE:
OnReverb(B_REVERB_NONE);
break;
case MSG_REVERB_CLOSET:
OnReverb(B_REVERB_CLOSET);
break;
case MSG_REVERB_GARAGE:
OnReverb(B_REVERB_GARAGE);
break;
case MSG_REVERB_IGOR:
OnReverb(B_REVERB_BALLROOM);
break;
case MSG_REVERB_CAVERN:
OnReverb(B_REVERB_CAVERN);
break;
case MSG_REVERB_DUNGEON:
OnReverb(B_REVERB_DUNGEON);
break;
case MSG_VOLUME:
OnVolume();
break;
case B_SIMPLE_DATA:
OnDrop(message);
break;
default:
super::MessageReceived(message);
break;
}
}
void
MidiPlayerWindow::FrameMoved(BPoint origin)
{
super::FrameMoved(origin);
fWindowX = Frame().left;
fWindowY = Frame().top;
SaveSettings();
}
void
MidiPlayerWindow::MenusBeginning()
{
for (int32 t = fInputPopUpMenu->CountItems() - 1; t > 0; --t)
delete fInputPopUpMenu->RemoveItem(t);
// Note: if the selected endpoint no longer exists, then no endpoint is
// marked. However, we won't disconnect it until you choose another one.
fInputOffMenuItem->SetMarked(fInputId == -1);
int32 id = 0;
while (BMidiEndpoint* endpoint = BMidiRoster::NextEndpoint(&id)) {
if (endpoint->IsProducer()) {
BMessage* message = new BMessage(MSG_INPUT_CHANGED);
message->AddInt32("id", id);
BMenuItem* item = new BMenuItem(endpoint->Name(), message);
fInputPopUpMenu->AddItem(item);
item->SetMarked(fInputId == id);
}
endpoint->Release();
}
}
void
MidiPlayerWindow::CreateInputMenu()
{
fInputPopUpMenu = new BPopUpMenu("inputPopUp");
BMessage* message = new BMessage(MSG_INPUT_CHANGED);
message->AddInt32("id", -1);
fInputOffMenuItem = new BMenuItem(B_TRANSLATE("Off"), message);
fInputPopUpMenu->AddItem(fInputOffMenuItem);
fInputMenuField = new BMenuField(B_TRANSLATE("Live input:"),
fInputPopUpMenu);
}
void
MidiPlayerWindow::CreateReverbMenu()
{
BPopUpMenu* reverbPopUpMenu = new BPopUpMenu("reverbPopUp");
fReverbNoneMenuItem = new BMenuItem(B_TRANSLATE("None"),
new BMessage(MSG_REVERB_NONE));
fReverbClosetMenuItem = new BMenuItem(B_TRANSLATE("Closet"),
new BMessage(MSG_REVERB_CLOSET));
fReverbGarageMenuItem = new BMenuItem(B_TRANSLATE("Garage"),
new BMessage(MSG_REVERB_GARAGE));
fReverbIgorMenuItem = new BMenuItem(B_TRANSLATE("Igor's lab"),
new BMessage(MSG_REVERB_IGOR));
fReverbCavern = new BMenuItem(B_TRANSLATE("Cavern"),
new BMessage(MSG_REVERB_CAVERN));
fReverbDungeon = new BMenuItem(B_TRANSLATE("Dungeon"),
new BMessage(MSG_REVERB_DUNGEON));
reverbPopUpMenu->AddItem(fReverbNoneMenuItem);
reverbPopUpMenu->AddItem(fReverbClosetMenuItem);
reverbPopUpMenu->AddItem(fReverbGarageMenuItem);
reverbPopUpMenu->AddItem(fReverbIgorMenuItem);
reverbPopUpMenu->AddItem(fReverbCavern);
reverbPopUpMenu->AddItem(fReverbDungeon);
fReverbMenuField = new BMenuField(B_TRANSLATE("Reverb:"), reverbPopUpMenu);
}
void
MidiPlayerWindow::CreateViews()
{
// Set up needed views
fScopeView = new ScopeView();
fShowScopeCheckBox = new BCheckBox("showScope", B_TRANSLATE("Scope"),
new BMessage(MSG_SHOW_SCOPE));
fShowScopeCheckBox->SetValue(B_CONTROL_ON);
CreateInputMenu();
CreateReverbMenu();
fVolumeSlider = new BSlider("volumeSlider", NULL, NULL, 0, 100,
B_HORIZONTAL);
rgb_color color = (rgb_color){ 152, 152, 255 };
fVolumeSlider->UseFillColor(true, &color);
fVolumeSlider->SetModificationMessage(new BMessage(MSG_VOLUME));
fVolumeSlider->SetExplicitMinSize(
BSize(fScopeView->Frame().Width(), B_SIZE_UNSET));
fPlayButton = new BButton("playButton", B_TRANSLATE("Play"),
new BMessage(MSG_PLAY_STOP));
fPlayButton->SetEnabled(false);
BStringView* volumeLabel = new BStringView(NULL, B_TRANSLATE("Volume:"));
volumeLabel->SetAlignment(B_ALIGN_LEFT);
// Build the layout
BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
.Add(fScopeView)
.AddGroup(B_VERTICAL, 0)
.AddGrid(B_USE_DEFAULT_SPACING, B_USE_SMALL_SPACING)
.Add(fShowScopeCheckBox, 1, 0)
.Add(fReverbMenuField->CreateLabelLayoutItem(), 0, 1)
.AddGroup(B_HORIZONTAL, 0.0f, 1, 1)
.Add(fReverbMenuField->CreateMenuBarLayoutItem())
.AddGlue()
.End()
.Add(fInputMenuField->CreateLabelLayoutItem(), 0, 2)
.AddGroup(B_HORIZONTAL, 0.0f, 1, 2)
.Add(fInputMenuField->CreateMenuBarLayoutItem())
.AddGlue()
.End()
.Add(volumeLabel, 0, 3)
.Add(fVolumeSlider, 0, 4, 2, 1)
.End()
.AddGlue()
.SetInsets(B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING,
B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING)
.End()
.Add(new BSeparatorView(B_HORIZONTAL))
.AddGroup(B_VERTICAL, 0)
.Add(fPlayButton)
.SetInsets(0, B_USE_DEFAULT_SPACING, 0, B_USE_WINDOW_SPACING)
.End()
.End();
}
void
MidiPlayerWindow::InitControls()
{
Lock();
fShowScopeCheckBox->SetValue(fScopeEnabled ? B_CONTROL_ON : B_CONTROL_OFF);
fScopeView->SetEnabled(fScopeEnabled);
fInputOffMenuItem->SetMarked(true);
fReverbNoneMenuItem->SetMarked(fReverbMode == B_REVERB_NONE);
fReverbClosetMenuItem->SetMarked(fReverbMode == B_REVERB_CLOSET);
fReverbGarageMenuItem->SetMarked(fReverbMode == B_REVERB_GARAGE);
fReverbIgorMenuItem->SetMarked(fReverbMode == B_REVERB_BALLROOM);
fReverbCavern->SetMarked(fReverbMode == B_REVERB_CAVERN);
fReverbDungeon->SetMarked(fReverbMode == B_REVERB_DUNGEON);
be_synth->SetReverb(fReverbMode);
fVolumeSlider->SetValue(fVolume);
if (fWindowX != -1 && fWindowY != -1)
MoveTo(fWindowX, fWindowY);
else
CenterOnScreen();
Unlock();
}
void
MidiPlayerWindow::LoadSettings()
{
BFile file(SETTINGS_FILE, B_READ_ONLY);
if (file.InitCheck() != B_OK || file.Lock() != B_OK)
return;
file.ReadAttr("Scope", B_BOOL_TYPE, 0, &fScopeEnabled, sizeof(bool));
file.ReadAttr("Reverb", B_INT32_TYPE, 0, &fReverbMode, sizeof(int32));
file.ReadAttr("Volume", B_INT32_TYPE, 0, &fWindowX, sizeof(int32));
file.ReadAttr("WindowX", B_FLOAT_TYPE, 0, &fWindowX, sizeof(float));
file.ReadAttr("WindowY", B_FLOAT_TYPE, 0, &fWindowY, sizeof(float));
file.Unlock();
}
void
MidiPlayerWindow::SaveSettings()
{
BFile file(SETTINGS_FILE, B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
if (file.InitCheck() != B_OK || file.Lock() != B_OK)
return;
file.WriteAttr("Scope", B_BOOL_TYPE, 0, &fScopeEnabled, sizeof(bool));
file.WriteAttr("Reverb", B_INT32_TYPE, 0, &fReverbMode, sizeof(int32));
file.WriteAttr("Volume", B_INT32_TYPE, 0, &fVolume, sizeof(int32));
file.WriteAttr("WindowX", B_FLOAT_TYPE, 0, &fWindowX, sizeof(float));
file.WriteAttr("WindowY", B_FLOAT_TYPE, 0, &fWindowY, sizeof(float));
file.Sync();
file.Unlock();
}
void
MidiPlayerWindow::LoadFile(entry_ref* ref)
{
if (fIsPlaying) {
fScopeView->SetPlaying(false);
fScopeView->Invalidate();
UpdateIfNeeded();
StopSynth();
}
fMidiSynthFile.UnloadFile();
if (fMidiSynthFile.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.
fMidiSynthFile.SetVolume(fVolume / 100.0f);
fPlayButton->SetEnabled(true);
fPlayButton->SetLabel(B_TRANSLATE("Stop"));
fScopeView->SetHaveFile(true);
fScopeView->SetPlaying(true);
fScopeView->Invalidate();
StartSynth();
} else {
fPlayButton->SetEnabled(false);
fPlayButton->SetLabel(B_TRANSLATE("Play"));
fScopeView->SetHaveFile(false);
fScopeView->SetPlaying(false);
fScopeView->Invalidate();
BAlert* alert = new BAlert(NULL, B_TRANSLATE("Could not load song"),
B_TRANSLATE("OK"), NULL, NULL,
B_WIDTH_AS_USUAL, B_STOP_ALERT);
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
}
}
void
MidiPlayerWindow::StartSynth()
{
fMidiSynthFile.Start();
fMidiSynthFile.SetFileHook(_StopHook, (int32)(addr_t)this);
fIsPlaying = true;
}
void
MidiPlayerWindow::StopSynth()
{
if (!fMidiSynthFile.IsFinished())
fMidiSynthFile.Fade();
fIsPlaying = false;
}
void
MidiPlayerWindow::_StopHook(int32 arg)
{
((MidiPlayerWindow*)(addr_t)arg)->StopHook();
}
void
MidiPlayerWindow::StopHook()
{
if (Lock()) {
// we may be called from the synth's thread
fIsPlaying = false;
fScopeView->SetPlaying(false);
fScopeView->Invalidate();
fPlayButton->SetEnabled(true);
fPlayButton->SetLabel(B_TRANSLATE("Play"));
Unlock();
}
}
void
MidiPlayerWindow::OnPlayStop()
{
if (fIsPlaying) {
fPlayButton->SetEnabled(false);
fScopeView->SetPlaying(false);
fScopeView->Invalidate();
UpdateIfNeeded();
StopSynth();
} else {
fPlayButton->SetLabel(B_TRANSLATE("Stop"));
fScopeView->SetPlaying(true);
fScopeView->Invalidate();
StartSynth();
}
}
void
MidiPlayerWindow::OnShowScope()
{
fScopeEnabled = !fScopeEnabled;
fScopeView->SetEnabled(fScopeEnabled);
fScopeView->Invalidate();
SaveSettings();
}
void
MidiPlayerWindow::OnInputChanged(BMessage* message)
{
int32 newId;
if (message->FindInt32("id", &newId) == B_OK) {
BMidiProducer* endpoint = BMidiRoster::FindProducer(fInputId);
if (endpoint != NULL) {
endpoint->Disconnect(fSynthBridge);
endpoint->Release();
}
fInputId = newId;
endpoint = BMidiRoster::FindProducer(fInputId);
if (endpoint != NULL) {
if (!fInstrumentLoaded) {
fScopeView->SetLoading(true);
fScopeView->Invalidate();
UpdateIfNeeded();
fSynthBridge->Init(B_BIG_SYNTH);
fInstrumentLoaded = true;
fScopeView->SetLoading(false);
fScopeView->Invalidate();
}
endpoint->Connect(fSynthBridge);
endpoint->Release();
fScopeView->SetLiveInput(true);
fScopeView->Invalidate();
} else {
fScopeView->SetLiveInput(false);
fScopeView->Invalidate();
}
}
}
void
MidiPlayerWindow::OnReverb(reverb_mode mode)
{
fReverbMode = mode;
be_synth->SetReverb(fReverbMode);
SaveSettings();
}
void
MidiPlayerWindow::OnVolume()
{
fVolume = fVolumeSlider->Value();
fMidiSynthFile.SetVolume(fVolume / 100.0f);
SaveSettings();
}
void
MidiPlayerWindow::OnDrop(BMessage* message)
{
entry_ref ref;
if (message->FindRef("refs", &ref) == B_OK)
LoadFile(&ref);
}