From 9282400ff444c8d85c264f0f5fd16d1c639b7fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20A=C3=9Fmus?= Date: Sun, 6 Jul 2008 18:29:42 +0000 Subject: [PATCH] * The SeekSlider did not update the knob when it was resized. * Subtile visual improvements to the SeekSlider. * Added a PeakView for displaying the audio peaks that are produced by the AudioProducer. * A MessageEvent can now directly take a custom BMessage for delivery. * The peak notification mechanism is a bit separate from the rest of the Controller notification design, since the notification delivery should be delayed until the audio is actually audible. I may change this quick and dirty design though, since it is not so nice. The target time could also be part of the message and be handled at a different stage, but that would make it less efficient. * Layout improvements to the playback controls. * Code cleanup here and there, changed some license statements. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@26280 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/apps/mediaplayer/ControllerView.cpp | 2 +- src/apps/mediaplayer/Jamfile | 1 + src/apps/mediaplayer/MainWin.cpp | 6 + .../mediaplayer/TransportControlGroup.cpp | 160 +++---- src/apps/mediaplayer/TransportControlGroup.h | 19 +- src/apps/mediaplayer/interface/PeakView.cpp | 429 ++++++++++++++++++ src/apps/mediaplayer/interface/PeakView.h | 92 ++++ src/apps/mediaplayer/interface/SeekSlider.cpp | 22 +- src/apps/mediaplayer/interface/SeekSlider.h | 22 +- .../media_node_framework/NodeManager.cpp | 15 +- .../media_node_framework/NodeManager.h | 4 + .../audio/AudioProducer.cpp | 49 +- .../audio/AudioProducer.h | 10 + src/apps/mediaplayer/support/MessageEvent.cpp | 13 +- src/apps/mediaplayer/support/MessageEvent.h | 9 +- 15 files changed, 738 insertions(+), 115 deletions(-) create mode 100644 src/apps/mediaplayer/interface/PeakView.cpp create mode 100644 src/apps/mediaplayer/interface/PeakView.h diff --git a/src/apps/mediaplayer/ControllerView.cpp b/src/apps/mediaplayer/ControllerView.cpp index a89228aca7..fbd3b5bfdc 100644 --- a/src/apps/mediaplayer/ControllerView.cpp +++ b/src/apps/mediaplayer/ControllerView.cpp @@ -32,7 +32,7 @@ ControllerView::ControllerView(BRect frame, Controller* controller, Playlist* playlist) - : TransportControlGroup(frame) + : TransportControlGroup(frame, true, true, false) , fController(controller) , fPlaylist(playlist) , fPlaylistObserver(new PlaylistObserver(this)) diff --git a/src/apps/mediaplayer/Jamfile b/src/apps/mediaplayer/Jamfile index b465efd6d4..db5f6eba77 100644 --- a/src/apps/mediaplayer/Jamfile +++ b/src/apps/mediaplayer/Jamfile @@ -26,6 +26,7 @@ for sourceDir in $(sourceDirs) { Application MediaPlayer : # interface DrawingTidbits.cpp + PeakView.cpp SeekSlider.cpp TransportButton.cpp VolumeSlider.cpp diff --git a/src/apps/mediaplayer/MainWin.cpp b/src/apps/mediaplayer/MainWin.cpp index c8aaaa284a..28e2017a50 100644 --- a/src/apps/mediaplayer/MainWin.cpp +++ b/src/apps/mediaplayer/MainWin.cpp @@ -41,8 +41,10 @@ #include #include +#include "AudioProducer.h" #include "ControllerObserver.h" #include "MainApp.h" +#include "PeakView.h" #include "PlaylistObserver.h" #include "PlaylistWindow.h" #include "SettingsWindow.h" @@ -170,6 +172,9 @@ MainWin::MainWin() fPlaylist->AddListener(fPlaylistObserver); fController->SetVideoView(fVideoView); fController->AddListener(fControllerObserver); + PeakView* peakView = fControls->GetPeakView(); + peakView->SetPeakNotificationWhat(MSG_PEAK_NOTIFICATION); + fController->SetPeakListener(peakView); // printf("fMenuBarHeight %d\n", fMenuBarHeight); // printf("fControlsHeight %d\n", fControlsHeight); @@ -201,6 +206,7 @@ MainWin::~MainWin() fPlaylist->RemoveListener(fPlaylistObserver); fController->RemoveListener(fControllerObserver); + fController->SetPeakListener(NULL); // give the views a chance to detach from any notifiers // before we delete them diff --git a/src/apps/mediaplayer/TransportControlGroup.cpp b/src/apps/mediaplayer/TransportControlGroup.cpp index f73611eda9..5f924245a2 100644 --- a/src/apps/mediaplayer/TransportControlGroup.cpp +++ b/src/apps/mediaplayer/TransportControlGroup.cpp @@ -1,13 +1,10 @@ /* - * Copyright 2006, Haiku. - * Distributed under the terms of the MIT License. - * - * Authors: - * Stephan Aßmus + * Copyright © 2006-2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT License. */ // NOTE: Based on my code in the BeOS interface for the VLC media player -// that I did during the VLC 0.4.3 - 0.4.6 times. Code not done by me +// that I did during the VLC 0.4.3 - 0.4.6 times. Code not written by me // removed. -Stephan Aßmus #include "TransportControlGroup.h" @@ -18,6 +15,7 @@ #include #include "ButtonBitmaps.h" +#include "PeakView.h" #include "PlaybackState.h" #include "SeekSlider.h" #include "TransportButton.h" @@ -53,63 +51,55 @@ enum { // constructor TransportControlGroup::TransportControlGroup(BRect frame, bool useSkipButtons, - bool useWindButtons) + bool usePeakView, bool useWindButtons) : BView(frame, "transport control group", B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP, B_WILL_DRAW | B_FRAME_EVENTS) , fBottomControlHeight(0.0) + , fPeakViewMinWidth(0.0) { SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); frame.Set(0.0, 0.0, 10.0, 10.0); // Seek Slider - fSeekSlider = new SeekSlider(frame, "seek slider", - new BMessage(MSG_SEEK), - 0, kPositionFactor); + fSeekSlider = new SeekSlider(frame, "seek slider", new BMessage(MSG_SEEK), + 0, kPositionFactor); fSeekSlider->ResizeToPreferred(); AddChild(fSeekSlider); // Buttons - if (useSkipButtons) { - // Skip Back - frame.right = kRewindBitmapWidth - 1; - frame.bottom = kRewindBitmapHeight - 1; + if (useSkipButtons) { + // Skip Back + frame.right = kRewindBitmapWidth - 1; + frame.bottom = kRewindBitmapHeight - 1; fBottomControlHeight = kRewindBitmapHeight - 1.0; - fSkipBack = new TransportButton(frame, B_EMPTY_STRING, - kSkipBackBitmapBits, - kPressedSkipBackBitmapBits, - kDisabledSkipBackBitmapBits, - new BMessage(MSG_SKIP_BACKWARDS)); - AddChild(fSkipBack); - - // Skip Foward - fSkipForward = new TransportButton(frame, B_EMPTY_STRING, - kSkipForwardBitmapBits, - kPressedSkipForwardBitmapBits, - kDisabledSkipForwardBitmapBits, - new BMessage(MSG_SKIP_FORWARD)); - AddChild(fSkipForward); - } else { - fSkipBack = NULL; - fSkipForward = NULL; + fSkipBack = new TransportButton(frame, B_EMPTY_STRING, + kSkipBackBitmapBits, kPressedSkipBackBitmapBits, + kDisabledSkipBackBitmapBits, new BMessage(MSG_SKIP_BACKWARDS)); + AddChild(fSkipBack); + + // Skip Foward + fSkipForward = new TransportButton(frame, B_EMPTY_STRING, + kSkipForwardBitmapBits, kPressedSkipForwardBitmapBits, + kDisabledSkipForwardBitmapBits, new BMessage(MSG_SKIP_FORWARD)); + AddChild(fSkipForward); + } else { + fSkipBack = NULL; + fSkipForward = NULL; } if (useWindButtons) { // Forward fForward = new TransportButton(frame, B_EMPTY_STRING, - kForwardBitmapBits, - kPressedForwardBitmapBits, - kDisabledForwardBitmapBits, - new BMessage(MSG_FORWARD)); + kForwardBitmapBits, kPressedForwardBitmapBits, + kDisabledForwardBitmapBits, new BMessage(MSG_FORWARD)); AddChild(fForward); - + // Rewind fRewind = new TransportButton(frame, B_EMPTY_STRING, - kRewindBitmapBits, - kPressedRewindBitmapBits, - kDisabledRewindBitmapBits, - new BMessage(MSG_REWIND)); + kRewindBitmapBits, kPressedRewindBitmapBits, + kDisabledRewindBitmapBits, new BMessage(MSG_REWIND)); AddChild(fRewind); } else { fForward = NULL; @@ -117,56 +107,56 @@ TransportControlGroup::TransportControlGroup(BRect frame, bool useSkipButtons, } // Play Pause - frame.right = kPlayPauseBitmapWidth - 1; - frame.bottom = kPlayPauseBitmapHeight - 1; + frame.right = kPlayPauseBitmapWidth - 1; + frame.bottom = kPlayPauseBitmapHeight - 1; if (fBottomControlHeight < kPlayPauseBitmapHeight - 1.0) fBottomControlHeight = kPlayPauseBitmapHeight - 1.0; - fPlayPause = new PlayPauseButton(frame, B_EMPTY_STRING, - kPlayButtonBitmapBits, - kPressedPlayButtonBitmapBits, - kDisabledPlayButtonBitmapBits, - kPlayingPlayButtonBitmapBits, - kPressedPlayingPlayButtonBitmapBits, - kPausedPlayButtonBitmapBits, - kPressedPausedPlayButtonBitmapBits, - new BMessage(MSG_PLAY)); + fPlayPause = new PlayPauseButton(frame, B_EMPTY_STRING, + kPlayButtonBitmapBits, kPressedPlayButtonBitmapBits, + kDisabledPlayButtonBitmapBits, kPlayingPlayButtonBitmapBits, + kPressedPlayingPlayButtonBitmapBits, kPausedPlayButtonBitmapBits, + kPressedPausedPlayButtonBitmapBits, new BMessage(MSG_PLAY)); - AddChild(fPlayPause); + AddChild(fPlayPause); - // Stop - frame.right = kStopBitmapWidth - 1; - frame.bottom = kStopBitmapHeight - 1; + // Stop + frame.right = kStopBitmapWidth - 1; + frame.bottom = kStopBitmapHeight - 1; if (fBottomControlHeight < kStopBitmapHeight - 1.0) fBottomControlHeight = kStopBitmapHeight - 1.0; - fStop = new TransportButton(frame, B_EMPTY_STRING, - kStopButtonBitmapBits, - kPressedStopButtonBitmapBits, - kDisabledStopButtonBitmapBits, - new BMessage(MSG_STOP)); + fStop = new TransportButton(frame, B_EMPTY_STRING, kStopButtonBitmapBits, + kPressedStopButtonBitmapBits, kDisabledStopButtonBitmapBits, + new BMessage(MSG_STOP)); AddChild(fStop); // Mute - frame.right = kSpeakerIconBitmapWidth - 1; - frame.bottom = kSpeakerIconBitmapHeight - 1; + frame.right = kSpeakerIconBitmapWidth - 1; + frame.bottom = kSpeakerIconBitmapHeight - 1; if (fBottomControlHeight < kSpeakerIconBitmapHeight - 1.0) fBottomControlHeight = kSpeakerIconBitmapHeight - 1.0; - fMute = new TransportButton(frame, B_EMPTY_STRING, - kSpeakerIconBits, - kPressedSpeakerIconBits, - kSpeakerIconBits, - new BMessage(MSG_SET_MUTE)); + fMute = new TransportButton(frame, B_EMPTY_STRING, kSpeakerIconBits, + kPressedSpeakerIconBits, kSpeakerIconBits, new BMessage(MSG_SET_MUTE)); AddChild(fMute); - // Volume Slider + // Volume Slider fVolumeSlider = new VolumeSlider(BRect(0.0, 0.0, VOLUME_MIN_WIDTH, - kVolumeSliderBitmapHeight - 1.0), - "volume slider", - _DbToGain(_ExponentialToLinear(kVolumeDbMin)) * kVolumeFactor, - _DbToGain(_ExponentialToLinear(kVolumeDbMax)) * kVolumeFactor, - new BMessage(MSG_SET_VOLUME)); - fVolumeSlider->SetValue(_DbToGain(_ExponentialToLinear(0.0)) * kVolumeFactor); + kVolumeSliderBitmapHeight - 1.0), "volume slider", + _DbToGain(_ExponentialToLinear(kVolumeDbMin)) * kVolumeFactor, + _DbToGain(_ExponentialToLinear(kVolumeDbMax)) * kVolumeFactor, + new BMessage(MSG_SET_VOLUME)); + fVolumeSlider->SetValue(_DbToGain(_ExponentialToLinear(0.0)) + * kVolumeFactor); AddChild(fVolumeSlider); + + // Peak view + if (usePeakView) { + fPeakView = new PeakView("peak view", false, false); + AddChild(fPeakView); + fPeakView->GetPreferredSize(&fPeakViewMinWidth, NULL); + } else { + fPeakView = NULL; + } } // destructor @@ -469,6 +459,8 @@ TransportControlGroup::_LayoutControls(BRect frame) const minWidth += fSkipForward->Bounds().Width(); minWidth += fMute->Bounds().Width(); minWidth += VOLUME_MIN_WIDTH; + if (fPeakView) + minWidth += fPeakViewMinWidth; // layout seek slider r.bottom = r.top + fSeekSlider->Bounds().Height(); @@ -481,7 +473,8 @@ TransportControlGroup::_LayoutControls(BRect frame) const float currentWidth = frame.Width(); float space = (currentWidth - minWidth) / 6.0; // apply weighting - space = MIN_SPACE + (space - MIN_SPACE) / VOLUME_SLIDER_LAYOUT_WEIGHT; + space = min_c(MIN_SPACE + (space - MIN_SPACE) / VOLUME_SLIDER_LAYOUT_WEIGHT, + MIN_SPACE * 2.0); // layout controls with "space" inbetween r.left = frame.left; r.top = r.bottom + MIN_SPACE + 1.0; @@ -522,11 +515,24 @@ TransportControlGroup::_LayoutControls(BRect frame) const r.left = r.right + space + space; r.right = r.left + fMute->Bounds().Width(); _LayoutControl(fMute, r); + // volume slider r.left = r.right + SPEAKER_SLIDER_DIST; // keep speaker icon and volume slider attached - r.right = frame.right; + // layout volume slider + float peakViewWidth = 0.0; + if (fPeakView) + peakViewWidth = (frame.right - r.left) / 2 + space; + + r.right = frame.right - peakViewWidth; _LayoutControl(fVolumeSlider, r, true); + + if (fPeakView) { + peakViewWidth -= space; + r.left = r.right + space; + r.right = r.left + peakViewWidth; + _LayoutControl(fPeakView, r, true, true); + } } // _MinFrame @@ -547,6 +553,8 @@ TransportControlGroup::_MinFrame() const minWidth += fSkipForward->Bounds().Width() + MIN_SPACE + MIN_SPACE; minWidth += fMute->Bounds().Width() + SPEAKER_SLIDER_DIST; minWidth += VOLUME_MIN_WIDTH; + if (fPeakView) + minWidth += fPeakViewMinWidth; // add up height of seek slider and heighest control on bottom float minHeight = 2 * BORDER_INSET; diff --git a/src/apps/mediaplayer/TransportControlGroup.h b/src/apps/mediaplayer/TransportControlGroup.h index d45c1a4660..5ec85305b3 100644 --- a/src/apps/mediaplayer/TransportControlGroup.h +++ b/src/apps/mediaplayer/TransportControlGroup.h @@ -1,9 +1,6 @@ /* - * Copyright 2006, Haiku. - * Distributed under the terms of the MIT License. - * - * Authors: - * Stephan Aßmus + * Copyright © 2006-2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT License. */ // NOTE: Based on my code in the BeOS interface for the VLC media player @@ -15,6 +12,7 @@ #include +class PeakView; class PlayPauseButton; class TransportButton; class SeekSlider; @@ -33,8 +31,9 @@ enum { class TransportControlGroup : public BView { public: TransportControlGroup(BRect frame, - bool useSkipButtons = true, - bool useWindButtons = false); + bool useSkipButtons, + bool usePeakView, + bool useWindButtons); virtual ~TransportControlGroup(); // BView interface @@ -67,6 +66,9 @@ class TransportControlGroup : public BView { void SetVolume(float value); void SetPosition(float value); + PeakView* GetPeakView() const + { return fPeakView; } + private: void _LayoutControls(BRect frame) const; BRect _MinFrame() const; @@ -91,7 +93,7 @@ class TransportControlGroup : public BView { float _GainToDb(float gain); SeekSlider* fSeekSlider; - + PeakView* fPeakView; VolumeSlider* fVolumeSlider; TransportButton* fSkipBack; @@ -103,6 +105,7 @@ class TransportControlGroup : public BView { TransportButton* fMute; float fBottomControlHeight; + float fPeakViewMinWidth; }; #endif // TRANSPORT_CONTROL_GROUP_H diff --git a/src/apps/mediaplayer/interface/PeakView.cpp b/src/apps/mediaplayer/interface/PeakView.cpp new file mode 100644 index 0000000000..072d7ed546 --- /dev/null +++ b/src/apps/mediaplayer/interface/PeakView.cpp @@ -0,0 +1,429 @@ +/* + * Copyright (C) 1998-1999 Be Incorporated. All rights reseved. + * Distributed under the terms of the Be Sample Code license. + * + * Copyright (C) 2001-2008 Stephan Aßmus. All rights reserved. + * Distributed under the terms of the MIT license. + */ +#include "PeakView.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using std::nothrow; + +enum { + MSG_PULSE = 'puls', + MSG_LOCK_PEAKS = 'lpks' +}; + +// constructor +PeakView::PeakView(const char* name, bool useGlobalPulse, bool displayLabels) + : BView(BRect(0.0, 0.0, 155.0 + 4.0, 10.0 + 4.0), + name, B_FOLLOW_LEFT | B_FOLLOW_TOP, + useGlobalPulse ? B_WILL_DRAW | B_PULSE_NEEDED | B_FRAME_EVENTS + : B_WILL_DRAW | B_FRAME_EVENTS), + fUseGlobalPulse(useGlobalPulse), + fDisplayLabels(displayLabels), + fPeakLocked(false), + + fRefreshDelay(20000), + fPulse(NULL), + + fCurrentMaxL(0.0), + fLastMaxL(0.0), + fOvershotL(false), + + fCurrentMaxR(0.0), + fLastMaxR(0.0), + fOvershotR(false), + + fBackBitmap(new BBitmap(BRect(0.0, 0.0, 256.0, 18.0), B_CMAP8)), + fPeakNotificationWhat(0) +{ + GetFontHeight(&fFontHeight); + + SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + SetViewColor(B_TRANSPARENT_COLOR); + + FrameResized(Bounds().Width(), Bounds().Height()); + + if (fDisplayLabels) + ResizeBy(0, ceilf(fFontHeight.ascent + fFontHeight.descent)); +} + +// destructor +PeakView::~PeakView() +{ + delete fPulse; + delete fBackBitmap; +} + +// MessageReceived +void +PeakView::MessageReceived(BMessage* message) +{ + if (message->what == fPeakNotificationWhat) { + float maxL; + if (message->FindFloat("max", 0, &maxL) < B_OK) + maxL = 0.0; + float maxR; + if (message->FindFloat("max", 1, &maxR) < B_OK) + maxR = 0.0; + SetMax(maxL, maxR); + return; + } + + switch (message->what) { + case MSG_PULSE: + Pulse(); + break; + + case MSG_LOCK_PEAKS: + fPeakLocked = !fPeakLocked; + break; + + default: + BView::MessageReceived(message); + break; + } +} + +// AttachedToWindow +void +PeakView::AttachedToWindow() +{ + if (!fUseGlobalPulse) { + delete fPulse; + BMessage message(MSG_PULSE); + fPulse = new BMessageRunner(BMessenger(this), &message, + fRefreshDelay); + } +} + +// DetachedFromWindow +void +PeakView::DetachedFromWindow() +{ + delete fPulse; + fPulse = NULL; +} + +// MouseDown +void +PeakView::MouseDown(BPoint where) +{ + int32 buttons; + if (Window()->CurrentMessage()->FindInt32("buttons", &buttons) < B_OK) + buttons = B_PRIMARY_MOUSE_BUTTON; + + if (buttons & B_PRIMARY_MOUSE_BUTTON) { + fOvershotL = false; + fOvershotR = false; + fLastMaxL = fCurrentMaxL; + fLastMaxR = fCurrentMaxR; + } else if (buttons & B_TERTIARY_MOUSE_BUTTON) { + fPeakLocked = !fPeakLocked; + } else { + BPopUpMenu* menu = new BPopUpMenu("peak context menu"); + BMenuItem* item = new BMenuItem("Lock Peaks", + new BMessage(MSG_LOCK_PEAKS)); + item->SetMarked(fPeakLocked); + menu->AddItem(item); + menu->SetTargetForItems(this); + + menu->SetAsyncAutoDestruct(true); + menu->SetFont(be_plain_font); + + where = ConvertToScreen(where); + bool keepOpen = false; // ? + if (keepOpen) { + BRect mouseRect(where, where); + mouseRect.InsetBy(-3.0, -3.0); + where += BPoint(3.0, 3.0); + menu->Go(where, true, false, mouseRect, true); + } else { + where += BPoint(3.0, 3.0); + menu->Go(where, true, false, true); + } + } +} + +// Draw +void +PeakView::Draw(BRect area) +{ + rgb_color background = LowColor(); + rgb_color lightShadow = tint_color(background, B_DARKEN_1_TINT); + rgb_color darkShadow = tint_color(background, B_DARKEN_4_TINT); + rgb_color light = tint_color(background, B_LIGHTEN_MAX_TINT); + rgb_color black = tint_color(background, B_DARKEN_MAX_TINT); + BRect r(_BackBitmapFrame()); + float width = r.Width(); + r.InsetBy(-2.0, -2.0); + // frame + BeginLineArray(9); + AddLine(BPoint(r.left, r.bottom), BPoint(r.left, r.top), lightShadow); + AddLine(BPoint(r.left + 1.0, r.top), BPoint(r.right, r.top), + lightShadow); + AddLine(BPoint(r.right, r.top + 1.0), BPoint(r.right, r.bottom), + light); + AddLine(BPoint(r.right - 1.0, r.bottom), + BPoint(r.left + 1.0, r.bottom), light); + r.InsetBy(1.0, 1.0); + AddLine(BPoint(r.left, r.bottom), BPoint(r.left, r.top), darkShadow); + AddLine(BPoint(r.left + 1.0, r.top), BPoint(r.right, r.top), + darkShadow); + AddLine(BPoint(r.right, r.top + 1.0), BPoint(r.right, r.bottom), + lightShadow); + AddLine(BPoint(r.right - 1.0, r.bottom), + BPoint(r.left + 1.0, r.bottom), lightShadow); + r.InsetBy(1.0, 1.0); + AddLine(BPoint(r.left, (r.top + r.bottom) / 2.0), + BPoint(r.right, (r.top + r.bottom) / 2.0), black); + EndLineArray(); + + // peak bitmap + if (fBackBitmap) + _DrawBitmap(); + + // dB + if (fDisplayLabels) { + font_height fh; + GetFontHeight(&fh); + float y = Bounds().bottom; + y -= fh.descent; + DrawString("0", BPoint(4.0 + width - StringWidth("0"), y)); + DrawString("-6", BPoint(0.477 * width, y)); + DrawString("-12", BPoint(0.227 * width, y)); + } +} + +// FrameResized +void +PeakView::FrameResized(float width, float height) +{ + BRect bitmapFrame = _BackBitmapFrame(); + _ResizeBackBitmap(bitmapFrame.IntegerWidth() + 1); + _UpdateBackBitmap(); +} + +// Pulse +void +PeakView::Pulse() +{ + if (!fBackBitmap) + return; + + if (!fPeakLocked) { + if (fCurrentMaxL > fLastMaxL) + fLastMaxL = fCurrentMaxL; + if (fCurrentMaxR > fLastMaxR) + fLastMaxR = fCurrentMaxR; + } + _UpdateBackBitmap(); + fCurrentMaxL = 0.0; + fCurrentMaxR = 0.0; + + _DrawBitmap(); + Flush(); +} + +// GetPreferredSize +void +PeakView::GetPreferredSize(float* _width, float* _height) +{ + float minWidth = 0; + float minHeight = 0; + if (fBackBitmap) { + minWidth = 20 + 4; + minHeight = 3 + 4; + } + if (fDisplayLabels) { + font_height fh; + GetFontHeight(&fh); + minWidth = max_c(60.0, minWidth); + minHeight += ceilf(fh.ascent + fh.descent); + } + if (_width) + *_width = minWidth; + if (_height) + *_height = minHeight; +} + +// IsValid +bool +PeakView::IsValid() const +{ + return fBackBitmap && fBackBitmap->IsValid(); +} + +// SetPeakRefreshDelay +void +PeakView::SetPeakRefreshDelay(bigtime_t delay) +{ + if (fRefreshDelay == delay) + return; + fRefreshDelay = delay; + if (fPulse) + fPulse->SetInterval(fRefreshDelay); +} + +// SetPeakNotificationWhat +void +PeakView::SetPeakNotificationWhat(uint32 what) +{ + fPeakNotificationWhat = what; +} + +// SetMax +void +PeakView::SetMax(float maxL, float maxR) +{ + if (fCurrentMaxL < maxL) + fCurrentMaxL = maxL; + if (fCurrentMaxR < maxR) + fCurrentMaxR = maxR; + + if (fCurrentMaxL > 1.0) + fOvershotL = true; + if (fCurrentMaxR > 1.0) + fOvershotR = true; +} + +// #pragma mark - + +// _BackBitmapFrame +BRect +PeakView::_BackBitmapFrame() const +{ + BRect frame = Bounds(); + frame.InsetBy(2, 2); + if (fDisplayLabels) + frame.bottom -= ceilf(fFontHeight.ascent + fFontHeight.descent); + return frame; +} + +// _ResizeBackBitmap +void +PeakView::_ResizeBackBitmap(int32 width) +{ + if (fBackBitmap) { + if (fBackBitmap->Bounds().IntegerWidth() + 1 == width) + return; + } + delete fBackBitmap; + fBackBitmap = new (nothrow) BBitmap(BRect(0, 0, width - 1, 1), 0, B_RGB32); + if (!fBackBitmap || !fBackBitmap->IsValid()) { + delete fBackBitmap; + fBackBitmap = NULL; + return; + } + memset(fBackBitmap->Bits(), 0, fBackBitmap->BitsLength()); +} + +// _UpdateBackBitmap +void +PeakView::_UpdateBackBitmap() +{ + if (!fBackBitmap) + return; + + uint8* l = (uint8*)fBackBitmap->Bits(); + uint8* r = l + fBackBitmap->BytesPerRow(); + uint32 width = fBackBitmap->Bounds().IntegerWidth() + 1; + _RenderSpan(l, width, fCurrentMaxL, fLastMaxL, fOvershotL); + _RenderSpan(r, width, fCurrentMaxR, fLastMaxR ,fOvershotR); +} + +// _RenderSpan +void +PeakView:: _RenderSpan(uint8* span, uint32 width, float current, float peak, + bool overshot) +{ + uint8 emptyR = 15; + uint8 emptyG = 36; + uint8 emptyB = 16; + + uint8 fillR = 41; + uint8 fillG = 120; + uint8 fillB = 45; + + uint8 currentR = 45; + uint8 currentG = 255; + uint8 currentB = 45; + + uint8 lastR = 255; + uint8 lastG = 229; + uint8 lastB = 87; + + uint8 overR = 255; + uint8 overG = 89; + uint8 overB = 7; + + uint8 kFadeFactor = 180; + + uint32 split = (uint32)(current * (width - 2) + 0.5); + split += split & 1; + uint32 last = (uint32)(peak * (width - 2) + 0.5); + last += last & 1; + uint32 over = overshot ? width - 1 : width; + over += over & 1; + + for (uint32 x = 0; x < width - 1; x += 2) { + uint8 fadedB = (uint8)(((int)span[0] * kFadeFactor) >> 8); + uint8 fadedG = (uint8)(((int)span[1] * kFadeFactor) >> 8); + uint8 fadedR = (uint8)(((int)span[2] * kFadeFactor) >> 8); + if (x < split) { + span[0] = max_c(fillB, fadedB); + span[1] = max_c(fillG, fadedG); + span[2] = max_c(fillR, fadedR); + } else if (x == split) { + span[0] = currentB; + span[1] = currentG; + span[2] = currentR; + } else if (x > split) { + span[0] = max_c(emptyB, fadedB); + span[1] = max_c(emptyG, fadedG); + span[2] = max_c(emptyR, fadedR); + } + if (x == last) { + span[0] = lastB; + span[1] = lastG; + span[2] = lastR; + } + if (x == over) { + span[0] = overB; + span[1] = overG; + span[2] = overR; + } + span += 8; + } +} + +// _DrawBitmap +void +PeakView::_DrawBitmap() +{ + BRect r = _BackBitmapFrame(); + BRect topHalf = r; + topHalf.bottom = (r.top + r.bottom) / 2.0 - 1; + BRect bottomHalf = r; + bottomHalf.top = topHalf.bottom + 2; + BRect bitmapRect = fBackBitmap->Bounds(); + bitmapRect.bottom = bitmapRect.top; + DrawBitmapAsync(fBackBitmap, bitmapRect, topHalf); + bitmapRect.OffsetBy(0, 1); + DrawBitmapAsync(fBackBitmap, bitmapRect, bottomHalf); +} + + diff --git a/src/apps/mediaplayer/interface/PeakView.h b/src/apps/mediaplayer/interface/PeakView.h new file mode 100644 index 0000000000..224788109f --- /dev/null +++ b/src/apps/mediaplayer/interface/PeakView.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 1998-1999 Be Incorporated. All rights reseved. + * Distributed under the terms of the Be Sample Code license. + * + * Copyright (C) 2001-2008 Stephan Aßmus. All rights reserved. + * Distributed under the terms of the MIT license. + */ + +/*---------------------------------------------------------- +PURPOSE: +gui class for displaying stereo audio peak level info + +FEATURES: +- uses a bitmap, but not a view accepting one, to redraw +without flickering. +- can be configured to use it's own message runner instead +of the windows current pulse (in case you have text views in +the window as well, which use a slow pulse for cursor blinking) +- if used with own message runner, refresh delay is configurable +- can be used in a dynamic liblayout gui + +USAGE: +To display the peak level of your streaming audio, just +calculate the local maximum of both channels within your +audio buffer and call SetMax() once for every buffer. The +PeakView will take care of the rest. +min = 0.0 +max = 1.0 +----------------------------------------------------------*/ +#ifndef PEAK_VIEW_H +#define PEAL_VIEW_H + + +#include + +class BBitmap; +class BMessageRunner; + +class PeakView : public BView { +public: + PeakView(const char* name, + bool useGlobalPulse = true, + bool displayLabels = true); + virtual ~PeakView(); + + // BView interface + virtual void MessageReceived(BMessage* message); + virtual void AttachedToWindow(); + virtual void DetachedFromWindow(); + virtual void MouseDown(BPoint where); + virtual void Draw(BRect area); + virtual void FrameResized(float width, float height); + virtual void Pulse(); + virtual void GetPreferredSize(float* _width, + float* _height); + + // PeakView + bool IsValid() const; + void SetPeakRefreshDelay(bigtime_t delay); + void SetPeakNotificationWhat(uint32 what); + void SetMax(float maxL, float maxR); + + private: + BRect _BackBitmapFrame() const; + void _ResizeBackBitmap(int32 width); + void _UpdateBackBitmap(); + void _RenderSpan(uint8* span, uint32 width, + float current, float peak, bool overshot); + void _DrawBitmap(); + + bool fUseGlobalPulse; + bool fDisplayLabels; + bool fPeakLocked; + + bigtime_t fRefreshDelay; + BMessageRunner* fPulse; + + float fCurrentMaxL; + float fLastMaxL; + bool fOvershotL; + + float fCurrentMaxR; + float fLastMaxR; + bool fOvershotR; + + BBitmap* fBackBitmap; + font_height fFontHeight; + + uint32 fPeakNotificationWhat; +}; + +#endif // PEAK_VIEW_H diff --git a/src/apps/mediaplayer/interface/SeekSlider.cpp b/src/apps/mediaplayer/interface/SeekSlider.cpp index 35cdd344c8..c3314046bf 100644 --- a/src/apps/mediaplayer/interface/SeekSlider.cpp +++ b/src/apps/mediaplayer/interface/SeekSlider.cpp @@ -1,13 +1,10 @@ /* - * Copyright 2006, Haiku. - * Distributed under the terms of the MIT License. - * - * Authors: - * Stephan Aßmus + * Copyright © 2006-2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT License. */ // NOTE: Based on my code in the BeOS interface for the VLC media player -// that I did during the VLC 0.4.3 - 0.4.6 times. Code not done by me +// that I did during the VLC 0.4.3 - 0.4.6 times. Code not written by me // removed. -Stephan Aßmus #include "SeekSlider.h" @@ -32,7 +29,7 @@ const char* kDisabledSeekMessage = "Drop files to play"; SeekSlider::SeekSlider(BRect frame, const char* name, BMessage* message, int32 minValue, int32 maxValue) : BControl(frame, name, NULL, message, B_FOLLOW_LEFT | B_FOLLOW_TOP, - B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE) + B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS) , fTracking(false) , fLastTrackTime(0) , fKnobPos(_KnobPosFor(Bounds(), Value())) @@ -101,9 +98,9 @@ SeekSlider::Draw(BRect updateRect) rgb_color dotGrey = midShadow; rgb_color dotGreen = greenShadow; // draw frame - _StrokeFrame(r, softShadow, softShadow, softLight, softLight); + _StrokeFrame(r, softShadow, softShadow, light, light); r.InsetBy(1.0, 1.0); - _StrokeFrame(r, black, black, light, light); + _StrokeFrame(r, black, black, softShadow, softShadow); if (IsEnabled()) { // *** enabled look *** r.InsetBy(1.0, 1.0); @@ -280,6 +277,13 @@ SeekSlider::ResizeToPreferred() } +void +SeekSlider::FrameResized(float width, float height) +{ + _SetKnobPosition(_KnobPosFor(Bounds(), Value())); +} + + void SeekSlider::SetPosition(float position) { diff --git a/src/apps/mediaplayer/interface/SeekSlider.h b/src/apps/mediaplayer/interface/SeekSlider.h index 48148ec2b3..8ccbe8660a 100644 --- a/src/apps/mediaplayer/interface/SeekSlider.h +++ b/src/apps/mediaplayer/interface/SeekSlider.h @@ -1,9 +1,6 @@ /* - * Copyright 2006, Haiku. - * Distributed under the terms of the MIT License. - * - * Authors: - * Stephan Aßmus + * Copyright © 2006-2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef SEEK_SLIDER_H @@ -15,10 +12,8 @@ class SeekSlider : public BControl { public: SeekSlider(BRect frame, - const char* name, - BMessage* message, - int32 minValue, - int32 maxValue); + const char* name, BMessage* message, + int32 minValue, int32 maxValue); virtual ~SeekSlider(); @@ -28,9 +23,10 @@ class SeekSlider : public BControl { virtual void Draw(BRect updateRect); virtual void MouseDown(BPoint where); virtual void MouseMoved(BPoint where, uint32 transit, - const BMessage* dragMessage); + const BMessage* dragMessage); virtual void MouseUp(BPoint where); virtual void ResizeToPreferred(); + virtual void FrameResized(float width, float height); // SeekSlider void SetPosition(float position); @@ -41,10 +37,8 @@ private: int32 _KnobPosFor(BRect bounds, int32 value) const; void _StrokeFrame(BRect frame, - rgb_color left, - rgb_color top, - rgb_color right, - rgb_color bottom); + rgb_color left, rgb_color top, + rgb_color right, rgb_color bottom); void _SetKnobPosition(int32 knobPos); diff --git a/src/apps/mediaplayer/media_node_framework/NodeManager.cpp b/src/apps/mediaplayer/media_node_framework/NodeManager.cpp index a4e875296c..cfb4216a05 100644 --- a/src/apps/mediaplayer/media_node_framework/NodeManager.cpp +++ b/src/apps/mediaplayer/media_node_framework/NodeManager.cpp @@ -51,7 +51,8 @@ NodeManager::NodeManager() fVideoTarget(NULL), fAudioSupplier(NULL), fVideoSupplier(NULL), - fVideoBounds(0, 0, -1, -1) + fVideoBounds(0, 0, -1, -1), + fPeakListener(NULL) { } @@ -233,13 +234,22 @@ NodeManager::SetVolume(float percent) // our audio node... } +// SetPeakListener +void +NodeManager::SetPeakListener(BHandler* handler) +{ + fPeakListener = handler; + if (fAudioProducer) + fAudioProducer->SetPeakListener(fPeakListener); +} + // #pragma mark - // _SetUpNodes status_t NodeManager::_SetUpNodes(color_space preferredVideoFormat) { -printf("NodeManager::_SetUpNodes()\n"); + TRACE("NodeManager::_SetUpNodes()\n"); // find the media roster fStatus = B_OK; @@ -425,6 +435,7 @@ status_t NodeManager::_SetUpAudioNodes() { fAudioProducer = new AudioProducer("MediaPlayer Audio Out", fAudioSupplier); + fAudioProducer->SetPeakListener(fPeakListener); fStatus = fMediaRoster->RegisterNode(fAudioProducer); if (fStatus != B_OK) { print_error("unable to register audio producer node!\n", fStatus); diff --git a/src/apps/mediaplayer/media_node_framework/NodeManager.h b/src/apps/mediaplayer/media_node_framework/NodeManager.h index 0905221d4e..5bfee3fec7 100644 --- a/src/apps/mediaplayer/media_node_framework/NodeManager.h +++ b/src/apps/mediaplayer/media_node_framework/NodeManager.h @@ -62,6 +62,8 @@ class NodeManager : public PlaybackManager { virtual void SetVolume(float percent); + void SetPeakListener(BHandler* handler); + private: status_t _SetUpNodes(color_space preferredVideoFormat); status_t _SetUpVideoNodes( @@ -103,6 +105,8 @@ private: AudioSupplier* fAudioSupplier; VideoSupplier* fVideoSupplier; BRect fVideoBounds; + + BHandler* fPeakListener; }; diff --git a/src/apps/mediaplayer/media_node_framework/audio/AudioProducer.cpp b/src/apps/mediaplayer/media_node_framework/audio/AudioProducer.cpp index db52ad677f..fe20e15600 100644 --- a/src/apps/mediaplayer/media_node_framework/audio/AudioProducer.cpp +++ b/src/apps/mediaplayer/media_node_framework/audio/AudioProducer.cpp @@ -18,6 +18,8 @@ #include #include "AudioSupplier.h" +#include "EventQueue.h" +#include "MessageEvent.h" #define DEBUG_TO_FILE 0 @@ -94,7 +96,9 @@ AudioProducer::AudioProducer(const char* name, AudioSupplier* supplier, fFramesSent(0), fStartTime(0), fSupplier(supplier), - fRunning(false) + fRunning(false), + + fPeakListener(NULL) { TRACE("%p->AudioProducer::AudioProducer(%s, %p, %d)\n", this, name, supplier, lowLatency); @@ -661,6 +665,13 @@ AudioProducer::SetRunning(bool running) } +void +AudioProducer::SetPeakListener(BHandler* handler) +{ + fPeakListener = handler; +} + + // #pragma mark - @@ -739,6 +750,42 @@ AudioProducer::_FillNextBuffer(bigtime_t eventTime) } #endif // DEBUG_TO_FILE + if (fPeakListener + && fOutput.format.u.raw_audio.format + == media_raw_audio_format::B_AUDIO_FLOAT) { + // TODO: extend the peak notifier for other sample formats + int32 channels = fOutput.format.u.raw_audio.channel_count; + float max[channels]; + float min[channels]; + for (int32 i = 0; i < channels; i++) { + max[i] = -1.0; + min[i] = 1.0; + } + float* sample = (float*)buffer->Data(); + for (uint32 i = 0; i < frameCount; i++) { + for (int32 k = 0; k < channels; k++) { + if (*sample < min[k]) + min[k] = *sample; + if (*sample > max[k]) + max[k] = *sample; + sample++; + } + } + BMessage message(MSG_PEAK_NOTIFICATION); + for (int32 i = 0; i < channels; i++) { + float maxAbs = max_c(fabs(min[i]), fabs(max[i])); + message.AddFloat("max", maxAbs); + } + bigtime_t realTime = TimeSource()->RealTimeFor(performanceTime, 0); + realTime -= 2000; + // deliver event about one video frame earlier to account + // for latency between app_server and client + MessageEvent* event = new (nothrow) MessageEvent(realTime, + fPeakListener, message); + if (event != NULL) + EventQueue::Default().AddEvent(event); + } + return buffer; } diff --git a/src/apps/mediaplayer/media_node_framework/audio/AudioProducer.h b/src/apps/mediaplayer/media_node_framework/audio/AudioProducer.h index 16528a80b2..b0866f8270 100644 --- a/src/apps/mediaplayer/media_node_framework/audio/AudioProducer.h +++ b/src/apps/mediaplayer/media_node_framework/audio/AudioProducer.h @@ -13,6 +13,11 @@ #include class AudioSupplier; +class BHandler; + +enum { + MSG_PEAK_NOTIFICATION = 'pknt' +}; class AudioProducer : public BBufferProducer, public BMediaEventLooper { public: @@ -95,6 +100,9 @@ public: void SetRunning(bool running); + // AudioProducer + void SetPeakListener(BHandler* handler); + private: status_t _AllocateBuffers(media_format* format); BBuffer* _FillNextBuffer(bigtime_t eventTime); @@ -116,6 +124,8 @@ private: AudioSupplier* fSupplier; volatile bool fRunning; + + BHandler* fPeakListener; }; #endif // AUDIO_PRODUCER_H diff --git a/src/apps/mediaplayer/support/MessageEvent.cpp b/src/apps/mediaplayer/support/MessageEvent.cpp index bf17ec8e9c..e60bf419d9 100644 --- a/src/apps/mediaplayer/support/MessageEvent.cpp +++ b/src/apps/mediaplayer/support/MessageEvent.cpp @@ -11,7 +11,16 @@ MessageEvent::MessageEvent(bigtime_t time, BHandler* handler, uint32 command) : Event(time), AbstractLOAdapter(handler), - fCommand(command) + fMessage(command) +{ +} + + +MessageEvent::MessageEvent(bigtime_t time, BHandler* handler, + const BMessage& message) + : Event(time), + AbstractLOAdapter(handler), + fMessage(message) { } @@ -31,7 +40,7 @@ MessageEvent::~MessageEvent() void MessageEvent::Execute() { - BMessage msg(fCommand); + BMessage msg(fMessage); msg.AddInt64("time", Time()); DeliverMessage(msg); } diff --git a/src/apps/mediaplayer/support/MessageEvent.h b/src/apps/mediaplayer/support/MessageEvent.h index 96c5ada696..9f8ea7e9e3 100644 --- a/src/apps/mediaplayer/support/MessageEvent.h +++ b/src/apps/mediaplayer/support/MessageEvent.h @@ -7,12 +7,14 @@ #define MESSAGE_EVENT_H +#include + #include "AbstractLOAdapter.h" #include "Event.h" enum { - MSG_EVENT = 'evnt', + MSG_EVENT = 'evnt' }; @@ -21,6 +23,9 @@ class MessageEvent : public Event, public AbstractLOAdapter { MessageEvent(bigtime_t time, BHandler* handler, uint32 command = MSG_EVENT); + MessageEvent(bigtime_t time, + BHandler* handler, + const BMessage& message); MessageEvent(bigtime_t time, const BMessenger& messenger); virtual ~MessageEvent(); @@ -28,7 +33,7 @@ class MessageEvent : public Event, public AbstractLOAdapter { virtual void Execute(); private: - uint32 fCommand; + BMessage fMessage; }; #endif // MESSAGE_EVENT_H