1/*
2 * Copyright (C) 2001-2010 Stephan Aßmus. All rights reserved.
3 * Distributed under the terms of the MIT license.
4 *
5 * Copyright (C) 1998-1999 Be Incorporated. All rights reseved.
6 * Distributed under the terms of the Be Sample Code license.
7 */
8
9
10#include "PeakView.h"
11
12#include <new>
13#include <stdio.h>
14#include <string.h>
15
16#include <Bitmap.h>
17#include <Catalog.h>
18#include <ControlLook.h>
19#include <Locale.h>
20#include <MenuItem.h>
21#include <Message.h>
22#include <MessageRunner.h>
23#include <Messenger.h>
24#include <PopUpMenu.h>
25#include <Window.h>
26
27
28#undef B_TRANSLATION_CONTEXT
29#define B_TRANSLATION_CONTEXT "MediaPlayer-PeakView"
30
31
32using std::nothrow;
33
34
35enum {
36	MSG_PULSE		= 'puls',
37	MSG_LOCK_PEAKS	= 'lpks'
38};
39
40
41PeakView::PeakView(const char* name, bool useGlobalPulse, bool displayLabels)
42	:
43	BView(name, (useGlobalPulse ? B_PULSE_NEEDED : 0)
44		| B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
45	fUseGlobalPulse(useGlobalPulse),
46	fDisplayLabels(displayLabels),
47	fPeakLocked(false),
48
49	fRefreshDelay(20000),
50	fPulse(NULL),
51
52	fChannelInfos(NULL),
53	fChannelCount(0),
54	fGotData(true),
55
56	fBackBitmap(NULL),
57	fPeakNotificationWhat(0)
58{
59	GetFontHeight(&fFontHeight);
60
61	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
62	SetViewColor(B_TRANSPARENT_COLOR);
63
64	SetChannelCount(2);
65}
66
67
68PeakView::~PeakView()
69{
70	delete fPulse;
71	delete fBackBitmap;
72	delete[] fChannelInfos;
73}
74
75
76void
77PeakView::MessageReceived(BMessage* message)
78{
79	if (message->what == fPeakNotificationWhat) {
80		float max;
81		for (int32 i = 0; message->FindFloat("max", i, &max) == B_OK; i++)
82			SetMax(max, i);
83		fGotData = true;
84		return;
85	}
86
87	switch (message->what) {
88		case MSG_PULSE:
89			Pulse();
90			break;
91
92		case MSG_LOCK_PEAKS:
93			fPeakLocked = !fPeakLocked;
94			break;
95
96		default:
97			BView::MessageReceived(message);
98			break;
99	}
100}
101
102
103void
104PeakView::AttachedToWindow()
105{
106	if (!fUseGlobalPulse) {
107		delete fPulse;
108		BMessage message(MSG_PULSE);
109		fPulse = new BMessageRunner(BMessenger(this), &message,
110			fRefreshDelay);
111	}
112}
113
114
115void
116PeakView::DetachedFromWindow()
117{
118	delete fPulse;
119	fPulse = NULL;
120}
121
122
123void
124PeakView::MouseDown(BPoint where)
125{
126	int32 buttons;
127	if (Window()->CurrentMessage()->FindInt32("buttons", &buttons) < B_OK)
128		buttons = B_PRIMARY_MOUSE_BUTTON;
129
130	if (buttons & B_PRIMARY_MOUSE_BUTTON) {
131		// Reset the overshot flag and set the observed max to the current
132		// value.
133		for (uint32 i = 0; i < fChannelCount; i++) {
134			fChannelInfos[i].last_overshot_time = -5000000;
135			fChannelInfos[i].last_max = fChannelInfos[i].current_max;
136		}
137	} else if (buttons & B_TERTIARY_MOUSE_BUTTON) {
138		// Toggle locking of the observed max value.
139		fPeakLocked = !fPeakLocked;
140	} else {
141		// Display context menu
142		BPopUpMenu* menu = new BPopUpMenu("peak context menu");
143		BMenuItem* item = new BMenuItem(B_TRANSLATE("Lock Peaks"),
144			new BMessage(MSG_LOCK_PEAKS));
145		item->SetMarked(fPeakLocked);
146		menu->AddItem(item);
147		menu->SetTargetForItems(this);
148
149		menu->SetAsyncAutoDestruct(true);
150		menu->SetFont(be_plain_font);
151
152		where = ConvertToScreen(where);
153		bool keepOpen = false; // ?
154		if (keepOpen) {
155			BRect mouseRect(where, where);
156			mouseRect.InsetBy(-3.0, -3.0);
157			where += BPoint(3.0, 3.0);
158			menu->Go(where, true, false, mouseRect, true);
159		} else {
160			where += BPoint(3.0, 3.0);
161			menu->Go(where, true, false, true);
162		}
163	}
164}
165
166
167void
168PeakView::Draw(BRect updateRect)
169{
170	BRect r(_BackBitmapFrame());
171	float width = r.Width();
172	r.InsetBy(-2.0, -2.0);
173
174	be_control_look->DrawTextControlBorder(this, r, updateRect, LowColor());
175
176	// peak bitmap
177	if (fBackBitmap)
178		_DrawBitmap();
179
180	// dB labels
181	if (fDisplayLabels) {
182		font_height fh;
183		GetFontHeight(&fh);
184		float y = Bounds().bottom;
185		y -= fh.descent;
186		DrawString("0", BPoint(4.0 + width - StringWidth("0"), y));
187		DrawString("-6", BPoint(0.477 * width, y));
188		DrawString("-12", BPoint(0.227 * width, y));
189	}
190}
191
192
193void
194PeakView::FrameResized(float width, float height)
195{
196	BRect bitmapFrame = _BackBitmapFrame();
197	_ResizeBackBitmap(bitmapFrame.IntegerWidth() + 1, fChannelCount);
198	_UpdateBackBitmap();
199}
200
201
202void
203PeakView::Pulse()
204{
205	if (!fGotData)
206		return;
207
208	if (fBackBitmap == NULL)
209		return;
210
211	if (!fPeakLocked) {
212		for (uint32 i = 0; i < fChannelCount; i++) {
213			fChannelInfos[i].last_max *= 0.96f;
214			if (fChannelInfos[i].current_max > fChannelInfos[i].last_max)
215				fChannelInfos[i].last_max = fChannelInfos[i].current_max;
216		}
217	}
218	_UpdateBackBitmap();
219
220	for (uint32 i = 0; i < fChannelCount; i++)
221		fChannelInfos[i].current_max = 0.0f;
222	fGotData = false;
223
224	_DrawBitmap();
225	Flush();
226}
227
228
229BSize
230PeakView::MinSize()
231{
232	float minWidth = 20 + 4;
233	float minHeight = 2 * 8 - 1 + 4;
234	if (fDisplayLabels) {
235		font_height fh;
236		GetFontHeight(&fh);
237		minWidth = max_c(60.0, minWidth);
238		minHeight += ceilf(fh.ascent + fh.descent);
239	}
240	return BSize(minWidth, minHeight);
241}
242
243
244bool
245PeakView::IsValid() const
246{
247	return fBackBitmap != NULL && fBackBitmap->IsValid()
248		&& fChannelInfos != NULL;
249}
250
251
252void
253PeakView::SetPeakRefreshDelay(bigtime_t delay)
254{
255	if (fRefreshDelay == delay)
256		return;
257
258	fRefreshDelay = delay;
259
260	if (fPulse != NULL)
261		fPulse->SetInterval(fRefreshDelay);
262}
263
264
265void
266PeakView::SetPeakNotificationWhat(uint32 what)
267{
268	fPeakNotificationWhat = what;
269}
270
271
272void
273PeakView::SetChannelCount(uint32 channelCount)
274{
275	if (channelCount == fChannelCount)
276		return;
277
278	delete[] fChannelInfos;
279	fChannelInfos = new(std::nothrow) ChannelInfo[channelCount];
280	if (fChannelInfos != NULL) {
281		fChannelCount = channelCount;
282		for (uint32 i = 0; i < fChannelCount; i++) {
283			fChannelInfos[i].current_max = 0.0f;
284			fChannelInfos[i].last_max = 0.0f;
285			fChannelInfos[i].last_overshot_time = -5000000;
286		}
287		_ResizeBackBitmap(_BackBitmapFrame().IntegerWidth() + 1,
288			fChannelCount);
289	} else
290		fChannelCount = 0;
291}
292
293
294void
295PeakView::SetMax(float max, uint32 channel)
296{
297	if (channel >= fChannelCount)
298		return;
299
300	if (fChannelInfos[channel].current_max < max)
301		fChannelInfos[channel].current_max = max;
302
303	if (fChannelInfos[channel].current_max > 1.0)
304		fChannelInfos[channel].last_overshot_time = system_time();
305}
306
307
308// #pragma mark -
309
310
311BRect
312PeakView::_BackBitmapFrame() const
313{
314	BRect frame = Bounds();
315	frame.InsetBy(2, 2);
316	if (fDisplayLabels)
317		frame.bottom -= ceilf(fFontHeight.ascent + fFontHeight.descent);
318	return frame;
319}
320
321
322void
323PeakView::_ResizeBackBitmap(int32 width, int32 channels)
324{
325	if (fBackBitmap != NULL) {
326		if (fBackBitmap->Bounds().IntegerWidth() + 1 == width
327			&& fBackBitmap->Bounds().IntegerHeight() + 1 == channels) {
328			return;
329		}
330	}
331	if (channels <= 0)
332		channels = 2;
333
334	delete fBackBitmap;
335	BRect bounds(0, 0, width - 1, channels - 1);
336	fBackBitmap = new(std::nothrow) BBitmap(bounds, 0, B_RGB32);
337	if (fBackBitmap == NULL || !fBackBitmap->IsValid()) {
338		delete fBackBitmap;
339		fBackBitmap = NULL;
340		return;
341	}
342	memset(fBackBitmap->Bits(), 0, fBackBitmap->BitsLength());
343	fGotData = true;
344}
345
346
347void
348PeakView::_UpdateBackBitmap()
349{
350	if (!fBackBitmap)
351		return;
352
353	uint8* span = (uint8*)fBackBitmap->Bits();
354	uint32 width = fBackBitmap->Bounds().IntegerWidth() + 1;
355	for (uint32 i = 0; i < fChannelCount; i++) {
356		_RenderSpan(span, width, fChannelInfos[i].current_max,
357			fChannelInfos[i].last_max,
358			system_time() - fChannelInfos[i].last_overshot_time < 2000000);
359		span += fBackBitmap->BytesPerRow();
360	}
361}
362
363
364void
365PeakView:: _RenderSpan(uint8* span, uint32 width, float current, float peak,
366	bool overshot)
367{
368	uint8 emptyR = 15;
369	uint8 emptyG = 36;
370	uint8 emptyB = 16;
371
372	uint8 fillR = 41;
373	uint8 fillG = 120;
374	uint8 fillB = 45;
375
376	uint8 currentR = 45;
377	uint8 currentG = 255;
378	uint8 currentB = 45;
379
380	uint8 lastR = 255;
381	uint8 lastG = 229;
382	uint8 lastB = 87;
383
384	uint8 overR = 255;
385	uint8 overG = 89;
386	uint8 overB = 7;
387
388	uint8 kFadeFactor = 100;
389
390	uint32 evenWidth = width - width % 2;
391	uint32 split = (uint32)(current * (evenWidth - 1) + 0.5);
392	split += split & 1;
393	uint32 last = (uint32)(peak * (evenWidth - 1) + 0.5);
394	last += last & 1;
395	uint32 over = overshot ? evenWidth : evenWidth + 1;
396	over += over & 1;
397
398	for (uint32 x = 0; x < width; x += 2) {
399		uint8 fadedB = (uint8)(((int)span[0] * kFadeFactor) >> 8);
400		uint8 fadedG = (uint8)(((int)span[1] * kFadeFactor) >> 8);
401		uint8 fadedR = (uint8)(((int)span[2] * kFadeFactor) >> 8);
402		if (x < split) {
403			span[0] = max_c(fillB, fadedB);
404			span[1] = max_c(fillG, fadedG);
405			span[2] = max_c(fillR, fadedR);
406		} else if (x == split) {
407			span[0] = currentB;
408			span[1] = currentG;
409			span[2] = currentR;
410		} else if (x > split) {
411			span[0] = max_c(emptyB, fadedB);
412			span[1] = max_c(emptyG, fadedG);
413			span[2] = max_c(emptyR, fadedR);
414		}
415		if (x == last) {
416			span[0] = lastB;
417			span[1] = lastG;
418			span[2] = lastR;
419		}
420		if (x == over) {
421			span[0] = overB;
422			span[1] = overG;
423			span[2] = overR;
424		}
425		span += 8;
426	}
427}
428
429
430void
431PeakView::_DrawBitmap()
432{
433	SetHighColor(0, 0, 0);
434	BRect bitmapFrame = _BackBitmapFrame();
435	BRect bitmapRect = fBackBitmap->Bounds();
436	bitmapRect.bottom = bitmapRect.top;
437	float channelHeight = (bitmapFrame.Height() + 1) / fChannelCount;
438	for (uint32 i = 0; i < fChannelCount; i++) {
439		BRect viewRect(bitmapFrame);
440		viewRect.bottom = viewRect.top;
441		viewRect.top += floorf(i * channelHeight + 0.5);
442		if (i < fChannelCount - 1) {
443			viewRect.bottom += floorf((i + 1) * channelHeight + 0.5) - 2;
444			StrokeLine(BPoint(viewRect.left, viewRect.bottom + 1),
445				BPoint(viewRect.right, viewRect.bottom + 1));
446		} else
447			viewRect.bottom += floorf((i + 1) * channelHeight + 0.5) - 1;
448		DrawBitmapAsync(fBackBitmap, bitmapRect, viewRect);
449		bitmapRect.OffsetBy(0, 1);
450	}
451}
452
453
454