vfs: check userland buffers before reading them.
[haiku.git] / src / apps / midiplayer / MidiPlayerWindow.cpp
blob84bdef07fb714419fd1d89aa9a2bcf18e3d68bd2
1 /*
2 * Copyright (c) 2004 Matthijs Hollemans
3 * Copyright (c) 2008-2014 Haiku, Inc. All rights reserved.
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
25 #include "MidiPlayerWindow.h"
27 #include <Catalog.h>
28 #include <LayoutBuilder.h>
29 #include <Locale.h>
30 #include <MidiProducer.h>
31 #include <MidiRoster.h>
32 #include <SeparatorView.h>
33 #include <StorageKit.h>
34 #include <SpaceLayoutItem.h>
36 #include "MidiPlayerApp.h"
37 #include "ScopeView.h"
38 #include "SynthBridge.h"
41 #define _W(a) (a->Frame().Width())
42 #define _H(a) (a->Frame().Height())
45 #undef B_TRANSLATION_CONTEXT
46 #define B_TRANSLATION_CONTEXT "Main Window"
49 // #pragma mark - MidiPlayerWindow
52 MidiPlayerWindow::MidiPlayerWindow()
54 BWindow(BRect(0, 0, 1, 1), B_TRANSLATE_SYSTEM_NAME("MidiPlayer"),
55 B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_NOT_RESIZABLE
56 | B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS)
58 fIsPlaying = false;
59 fScopeEnabled = true;
60 fReverbMode = B_REVERB_BALLROOM;
61 fVolume = 75;
62 fWindowX = -1;
63 fWindowY = -1;
64 fInputId = -1;
65 fInstrumentLoaded = false;
67 be_synth->SetSamplingRate(44100);
69 fSynthBridge = new SynthBridge;
70 //fSynthBridge->Register();
72 CreateViews();
73 LoadSettings();
74 InitControls();
78 MidiPlayerWindow::~MidiPlayerWindow()
80 StopSynth();
82 //fSynthBridge->Unregister();
83 fSynthBridge->Release();
87 bool
88 MidiPlayerWindow::QuitRequested()
90 be_app->PostMessage(B_QUIT_REQUESTED);
91 return true;
95 void
96 MidiPlayerWindow::MessageReceived(BMessage* message)
98 switch (message->what) {
99 case MSG_PLAY_STOP:
100 OnPlayStop();
101 break;
103 case MSG_SHOW_SCOPE:
104 OnShowScope();
105 break;
107 case MSG_INPUT_CHANGED:
108 OnInputChanged(message);
109 break;
111 case MSG_REVERB_NONE:
112 OnReverb(B_REVERB_NONE);
113 break;
115 case MSG_REVERB_CLOSET:
116 OnReverb(B_REVERB_CLOSET);
117 break;
119 case MSG_REVERB_GARAGE:
120 OnReverb(B_REVERB_GARAGE);
121 break;
123 case MSG_REVERB_IGOR:
124 OnReverb(B_REVERB_BALLROOM);
125 break;
127 case MSG_REVERB_CAVERN:
128 OnReverb(B_REVERB_CAVERN);
129 break;
131 case MSG_REVERB_DUNGEON:
132 OnReverb(B_REVERB_DUNGEON);
133 break;
135 case MSG_VOLUME:
136 OnVolume();
137 break;
139 case B_SIMPLE_DATA:
140 OnDrop(message);
141 break;
143 default:
144 super::MessageReceived(message);
145 break;
150 void
151 MidiPlayerWindow::FrameMoved(BPoint origin)
153 super::FrameMoved(origin);
154 fWindowX = Frame().left;
155 fWindowY = Frame().top;
156 SaveSettings();
160 void
161 MidiPlayerWindow::MenusBeginning()
163 for (int32 t = fInputPopUpMenu->CountItems() - 1; t > 0; --t)
164 delete fInputPopUpMenu->RemoveItem(t);
166 // Note: if the selected endpoint no longer exists, then no endpoint is
167 // marked. However, we won't disconnect it until you choose another one.
169 fInputOffMenuItem->SetMarked(fInputId == -1);
171 int32 id = 0;
172 while (BMidiEndpoint* endpoint = BMidiRoster::NextEndpoint(&id)) {
173 if (endpoint->IsProducer()) {
174 BMessage* message = new BMessage(MSG_INPUT_CHANGED);
175 message->AddInt32("id", id);
177 BMenuItem* item = new BMenuItem(endpoint->Name(), message);
178 fInputPopUpMenu->AddItem(item);
179 item->SetMarked(fInputId == id);
182 endpoint->Release();
187 void
188 MidiPlayerWindow::CreateInputMenu()
190 fInputPopUpMenu = new BPopUpMenu("inputPopUp");
192 BMessage* message = new BMessage(MSG_INPUT_CHANGED);
193 message->AddInt32("id", -1);
195 fInputOffMenuItem = new BMenuItem(B_TRANSLATE("Off"), message);
196 fInputPopUpMenu->AddItem(fInputOffMenuItem);
198 fInputMenuField = new BMenuField(B_TRANSLATE("Live input:"),
199 fInputPopUpMenu);
203 void
204 MidiPlayerWindow::CreateReverbMenu()
206 BPopUpMenu* reverbPopUpMenu = new BPopUpMenu("reverbPopUp");
207 fReverbNoneMenuItem = new BMenuItem(B_TRANSLATE("None"),
208 new BMessage(MSG_REVERB_NONE));
209 fReverbClosetMenuItem = new BMenuItem(B_TRANSLATE("Closet"),
210 new BMessage(MSG_REVERB_CLOSET));
211 fReverbGarageMenuItem = new BMenuItem(B_TRANSLATE("Garage"),
212 new BMessage(MSG_REVERB_GARAGE));
213 fReverbIgorMenuItem = new BMenuItem(B_TRANSLATE("Igor's lab"),
214 new BMessage(MSG_REVERB_IGOR));
215 fReverbCavern = new BMenuItem(B_TRANSLATE("Cavern"),
216 new BMessage(MSG_REVERB_CAVERN));
217 fReverbDungeon = new BMenuItem(B_TRANSLATE("Dungeon"),
218 new BMessage(MSG_REVERB_DUNGEON));
220 reverbPopUpMenu->AddItem(fReverbNoneMenuItem);
221 reverbPopUpMenu->AddItem(fReverbClosetMenuItem);
222 reverbPopUpMenu->AddItem(fReverbGarageMenuItem);
223 reverbPopUpMenu->AddItem(fReverbIgorMenuItem);
224 reverbPopUpMenu->AddItem(fReverbCavern);
225 reverbPopUpMenu->AddItem(fReverbDungeon);
227 fReverbMenuField = new BMenuField(B_TRANSLATE("Reverb:"), reverbPopUpMenu);
231 void
232 MidiPlayerWindow::CreateViews()
234 // Set up needed views
235 fScopeView = new ScopeView();
237 fShowScopeCheckBox = new BCheckBox("showScope", B_TRANSLATE("Scope"),
238 new BMessage(MSG_SHOW_SCOPE));
239 fShowScopeCheckBox->SetValue(B_CONTROL_ON);
241 CreateInputMenu();
242 CreateReverbMenu();
244 fVolumeSlider = new BSlider("volumeSlider", NULL, NULL, 0, 100,
245 B_HORIZONTAL);
246 rgb_color color = (rgb_color){ 152, 152, 255 };
247 fVolumeSlider->UseFillColor(true, &color);
248 fVolumeSlider->SetModificationMessage(new BMessage(MSG_VOLUME));
249 fVolumeSlider->SetExplicitMinSize(
250 BSize(fScopeView->Frame().Width(), B_SIZE_UNSET));
252 fPlayButton = new BButton("playButton", B_TRANSLATE("Play"),
253 new BMessage(MSG_PLAY_STOP));
254 fPlayButton->SetEnabled(false);
256 BStringView* volumeLabel = new BStringView(NULL, B_TRANSLATE("Volume:"));
257 volumeLabel->SetAlignment(B_ALIGN_LEFT);
259 // Build the layout
260 BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
261 .Add(fScopeView)
262 .AddGroup(B_VERTICAL, 0)
263 .AddGrid(B_USE_DEFAULT_SPACING, B_USE_SMALL_SPACING)
264 .Add(fShowScopeCheckBox, 1, 0)
266 .Add(fReverbMenuField->CreateLabelLayoutItem(), 0, 1)
267 .AddGroup(B_HORIZONTAL, 0.0f, 1, 1)
268 .Add(fReverbMenuField->CreateMenuBarLayoutItem())
269 .AddGlue()
270 .End()
272 .Add(fInputMenuField->CreateLabelLayoutItem(), 0, 2)
273 .AddGroup(B_HORIZONTAL, 0.0f, 1, 2)
274 .Add(fInputMenuField->CreateMenuBarLayoutItem())
275 .AddGlue()
276 .End()
278 .Add(volumeLabel, 0, 3)
279 .Add(fVolumeSlider, 0, 4, 2, 1)
280 .End()
281 .AddGlue()
282 .SetInsets(B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING,
283 B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING)
285 .End()
286 .Add(new BSeparatorView(B_HORIZONTAL))
287 .AddGroup(B_VERTICAL, 0)
288 .Add(fPlayButton)
289 .SetInsets(0, B_USE_DEFAULT_SPACING, 0, B_USE_WINDOW_SPACING)
290 .End()
291 .End();
295 void
296 MidiPlayerWindow::InitControls()
298 Lock();
300 fShowScopeCheckBox->SetValue(fScopeEnabled ? B_CONTROL_ON : B_CONTROL_OFF);
301 fScopeView->SetEnabled(fScopeEnabled);
303 fInputOffMenuItem->SetMarked(true);
305 fReverbNoneMenuItem->SetMarked(fReverbMode == B_REVERB_NONE);
306 fReverbClosetMenuItem->SetMarked(fReverbMode == B_REVERB_CLOSET);
307 fReverbGarageMenuItem->SetMarked(fReverbMode == B_REVERB_GARAGE);
308 fReverbIgorMenuItem->SetMarked(fReverbMode == B_REVERB_BALLROOM);
309 fReverbCavern->SetMarked(fReverbMode == B_REVERB_CAVERN);
310 fReverbDungeon->SetMarked(fReverbMode == B_REVERB_DUNGEON);
311 be_synth->SetReverb(fReverbMode);
313 fVolumeSlider->SetValue(fVolume);
315 if (fWindowX != -1 && fWindowY != -1)
316 MoveTo(fWindowX, fWindowY);
317 else
318 CenterOnScreen();
320 Unlock();
324 void
325 MidiPlayerWindow::LoadSettings()
327 BFile file(SETTINGS_FILE, B_READ_ONLY);
328 if (file.InitCheck() != B_OK || file.Lock() != B_OK)
329 return;
331 file.ReadAttr("Scope", B_BOOL_TYPE, 0, &fScopeEnabled, sizeof(bool));
332 file.ReadAttr("Reverb", B_INT32_TYPE, 0, &fReverbMode, sizeof(int32));
333 file.ReadAttr("Volume", B_INT32_TYPE, 0, &fWindowX, sizeof(int32));
334 file.ReadAttr("WindowX", B_FLOAT_TYPE, 0, &fWindowX, sizeof(float));
335 file.ReadAttr("WindowY", B_FLOAT_TYPE, 0, &fWindowY, sizeof(float));
337 file.Unlock();
341 void
342 MidiPlayerWindow::SaveSettings()
344 BFile file(SETTINGS_FILE, B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
345 if (file.InitCheck() != B_OK || file.Lock() != B_OK)
346 return;
348 file.WriteAttr("Scope", B_BOOL_TYPE, 0, &fScopeEnabled, sizeof(bool));
349 file.WriteAttr("Reverb", B_INT32_TYPE, 0, &fReverbMode, sizeof(int32));
350 file.WriteAttr("Volume", B_INT32_TYPE, 0, &fVolume, sizeof(int32));
351 file.WriteAttr("WindowX", B_FLOAT_TYPE, 0, &fWindowX, sizeof(float));
352 file.WriteAttr("WindowY", B_FLOAT_TYPE, 0, &fWindowY, sizeof(float));
354 file.Sync();
355 file.Unlock();
359 void
360 MidiPlayerWindow::LoadFile(entry_ref* ref)
362 if (fIsPlaying) {
363 fScopeView->SetPlaying(false);
364 fScopeView->Invalidate();
365 UpdateIfNeeded();
367 StopSynth();
370 fMidiSynthFile.UnloadFile();
372 if (fMidiSynthFile.LoadFile(ref) == B_OK) {
373 // Ideally, we would call SetVolume() in InitControls(),
374 // but for some reason that doesn't work: BMidiSynthFile
375 // will use the default volume instead. So we do it here.
376 fMidiSynthFile.SetVolume(fVolume / 100.0f);
378 fPlayButton->SetEnabled(true);
379 fPlayButton->SetLabel(B_TRANSLATE("Stop"));
380 fScopeView->SetHaveFile(true);
381 fScopeView->SetPlaying(true);
382 fScopeView->Invalidate();
384 StartSynth();
385 } else {
386 fPlayButton->SetEnabled(false);
387 fPlayButton->SetLabel(B_TRANSLATE("Play"));
388 fScopeView->SetHaveFile(false);
389 fScopeView->SetPlaying(false);
390 fScopeView->Invalidate();
392 BAlert* alert = new BAlert(NULL, B_TRANSLATE("Could not load song"),
393 B_TRANSLATE("OK"), NULL, NULL,
394 B_WIDTH_AS_USUAL, B_STOP_ALERT);
395 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
396 alert->Go();
401 void
402 MidiPlayerWindow::StartSynth()
404 fMidiSynthFile.Start();
405 fMidiSynthFile.SetFileHook(_StopHook, (int32)(addr_t)this);
406 fIsPlaying = true;
410 void
411 MidiPlayerWindow::StopSynth()
413 if (!fMidiSynthFile.IsFinished())
414 fMidiSynthFile.Fade();
416 fIsPlaying = false;
420 void
421 MidiPlayerWindow::_StopHook(int32 arg)
423 ((MidiPlayerWindow*)(addr_t)arg)->StopHook();
427 void
428 MidiPlayerWindow::StopHook()
430 Lock();
431 // we may be called from the synth's thread
433 fIsPlaying = false;
435 fScopeView->SetPlaying(false);
436 fScopeView->Invalidate();
437 fPlayButton->SetEnabled(true);
438 fPlayButton->SetLabel(B_TRANSLATE("Play"));
440 Unlock();
444 void
445 MidiPlayerWindow::OnPlayStop()
447 if (fIsPlaying) {
448 fPlayButton->SetEnabled(false);
449 fScopeView->SetPlaying(false);
450 fScopeView->Invalidate();
451 UpdateIfNeeded();
453 StopSynth();
454 } else {
455 fPlayButton->SetLabel(B_TRANSLATE("Stop"));
456 fScopeView->SetPlaying(true);
457 fScopeView->Invalidate();
459 StartSynth();
464 void
465 MidiPlayerWindow::OnShowScope()
467 fScopeEnabled = !fScopeEnabled;
468 fScopeView->SetEnabled(fScopeEnabled);
469 fScopeView->Invalidate();
470 SaveSettings();
474 void
475 MidiPlayerWindow::OnInputChanged(BMessage* message)
477 int32 newId;
478 if (message->FindInt32("id", &newId) == B_OK) {
479 BMidiProducer* endpoint = BMidiRoster::FindProducer(fInputId);
480 if (endpoint != NULL) {
481 endpoint->Disconnect(fSynthBridge);
482 endpoint->Release();
485 fInputId = newId;
487 endpoint = BMidiRoster::FindProducer(fInputId);
488 if (endpoint != NULL) {
489 if (!fInstrumentLoaded) {
490 fScopeView->SetLoading(true);
491 fScopeView->Invalidate();
492 UpdateIfNeeded();
494 fSynthBridge->Init(B_BIG_SYNTH);
495 fInstrumentLoaded = true;
497 fScopeView->SetLoading(false);
498 fScopeView->Invalidate();
501 endpoint->Connect(fSynthBridge);
502 endpoint->Release();
504 fScopeView->SetLiveInput(true);
505 fScopeView->Invalidate();
506 } else {
507 fScopeView->SetLiveInput(false);
508 fScopeView->Invalidate();
514 void
515 MidiPlayerWindow::OnReverb(reverb_mode mode)
517 fReverbMode = mode;
518 be_synth->SetReverb(fReverbMode);
519 SaveSettings();
523 void
524 MidiPlayerWindow::OnVolume()
526 fVolume = fVolumeSlider->Value();
527 fMidiSynthFile.SetVolume(fVolume / 100.0f);
528 SaveSettings();
532 void
533 MidiPlayerWindow::OnDrop(BMessage* message)
535 entry_ref ref;
536 if (message->FindRef("refs", &ref) == B_OK)
537 LoadFile(&ref);