/*
 * Copyright 2005, Jérôme Duval. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Inspired by SoundCapture from Be newsletter (Media Kit Basics: 
 * 	Consumers and Producers)
 */
 
#include <stdio.h>
#include <string.h>
#include <IconUtils.h>
#include <MimeType.h>
#include <Screen.h>
#include <Window.h>
#include "DrawingTidbits.h"
#include "ScopeView.h"
 
#define SAMPLES_COUNT 20000
 
//#define TRACE 1
#ifdef TRACE
#define TRACE(x...) printf(x)
#else
#define TRACE(x...)
#endif
 
ScopeView::ScopeView(BRect rect, uint32 resizeFlags)
	: BView(rect, "scope", resizeFlags, B_WILL_DRAW | B_FRAME_EVENTS),
	fThreadId(-1),
	fBitmap(NULL),
	fBitmapView(NULL),
	fIsRendering(false),
	fMediaTrack(NULL),
	fMainTime(0),
	fRightTime(1000000),
	fLeftTime(0),
	fTotalTime(1000000)
{
	fHeight = Bounds().Height();
}
 
 
ScopeView::~ScopeView()
{
	delete_sem(fRenderSem);
}
 
 
void
ScopeView::AttachedToWindow()
{
	SetViewColor(B_TRANSPARENT_COLOR);
	InitBitmap();
	Run();
}
 
 
void
ScopeView::DetachedFromWindow()
{
	Quit();
}
 
 
void
ScopeView::Draw(BRect updateRect)
{
	BRect bounds = Bounds();
	SetHighColor(0,0,0);
	
	if (!fIsRendering)
		DrawBitmapAsync(fBitmap, BPoint(0, 0));
	else 
		FillRect(bounds);
	
	float x = 0;
	if (fTotalTime != 0)
		x += (fMainTime - fLeftTime) * bounds.right
			/ (fRightTime - fLeftTime);
	SetHighColor(60,255,40);
	StrokeLine(BPoint(x, bounds.top), BPoint(x, bounds.bottom));
	
	Sync();
}
 
 
void
ScopeView::Run()
{
	fRenderSem = create_sem(0, "scope rendering");
	fThreadId = spawn_thread(&RenderLaunch, "Scope view", B_NORMAL_PRIORITY, 
		this);
	if (fThreadId < 0)
		return;
	resume_thread(fThreadId);
}
 
void
ScopeView::Quit()
{
	delete_sem(fRenderSem);
	snooze(10000);
	kill_thread(fThreadId);
}
 
 
 
int32
ScopeView::RenderLaunch(void *data)
{
	ScopeView *scope = (ScopeView*) data;
	scope->RenderLoop();
	return B_OK;
}
 
 
template<typename T, typename U>
void
ScopeView::ComputeRendering()
{
	int64 framesCount = fMediaTrack->CountFrames() / SAMPLES_COUNT;
	if (framesCount <= 0)
		return;
	T samples[fPlayFormat.u.raw_audio.buffer_size 
		/ (fPlayFormat.u.raw_audio.format 
		& media_raw_audio_format::B_AUDIO_SIZE_MASK)];
	int64 frames = 0;
	U sum = 0;
	int64 sumCount = 0;
	float middle = fHeight / 2;
	int32 previewMax = 0;
	//fMediaTrack->SeekToFrame(&frames);
 
	TRACE("begin computing\n");
 
	int32 previewIndex = 0;
 
	while (fIsRendering && fMediaTrack->ReadFrames(samples, &frames) == B_OK) {
		//TRACE("reading block\n");
		int64 framesIndex = 0;
 
		while (framesIndex < frames) {
			for (; framesIndex < frames && sumCount < framesCount; 
				framesIndex++, sumCount++) {
				sum += samples[2 * framesIndex];
				sum += samples[2 * framesIndex + 1];
			}
			
			if (previewIndex >= SAMPLES_COUNT)
				break;
			
			if (sumCount >= framesCount) {
				// TRACE("computing block %ld, sumCount %ld\n", previewIndex, 
				// sumCount);
				fPreview[previewIndex] = (int32)(sum 
					/ fPlayFormat.u.raw_audio.channel_count / framesCount);
				if (previewMax < fPreview[previewIndex])
					previewMax = fPreview[previewIndex];
				sumCount = 0;
				sum = 0;
				previewIndex++;
			}
		}
	}
	
	if (previewMax <= 0)
		return;
	for (int i = 0; i < SAMPLES_COUNT; i++)
		fPreview[i] = (int32)(fPreview[i] * 1.0 / previewMax 
			* middle + middle);
}
 
 
void
ScopeView::RenderLoop()
{
	while (acquire_sem(fRenderSem) == B_OK) {
		fIsRendering = true;
		
		switch (fPlayFormat.u.raw_audio.format) {
			case media_raw_audio_format::B_AUDIO_FLOAT:
				ComputeRendering<float, float>();
				break;
			case media_raw_audio_format::B_AUDIO_INT:
				ComputeRendering<int32, int64>();
				break;
			case media_raw_audio_format::B_AUDIO_SHORT:
				ComputeRendering<int16, int64>();
				break;
			case media_raw_audio_format::B_AUDIO_UCHAR:
				ComputeRendering<uchar, uint32>();
				break;
			case media_raw_audio_format::B_AUDIO_CHAR:
				ComputeRendering<char, int32>();
				break;
		}
				
		TRACE("finished computing, rendering\n");
		
		/* rendering */
		RenderBitmap();
		
		TRACE("rendering done\n");
 
		/* ask drawing */
		
		fIsRendering = false;
	
		if (Window()->LockWithTimeout(5000) == B_OK) {
			Invalidate();
			TRACE("invalidate done\n");
			Window()->Unlock();
		}
	}	
}
 
 
void 
ScopeView::SetMainTime(bigtime_t timestamp)
{
	fMainTime = timestamp;
	Invalidate();
	TRACE("invalidate done\n");
}
 
 
void 
ScopeView::SetTotalTime(bigtime_t timestamp, bool reset)
{
	fTotalTime = timestamp;
	if (reset) {
		fMainTime = 0;
		fLeftTime = 0;
		fRightTime = fTotalTime;
	}
	Invalidate();
	TRACE("invalidate done\n");
}
 
 
void 
ScopeView::SetLeftTime(bigtime_t timestamp)
{
	fLeftTime = timestamp;
	RenderBitmap();
	Invalidate();
	TRACE("invalidate done\n");
}
 
void 
ScopeView::SetRightTime(bigtime_t timestamp)
{
	fRightTime = timestamp;
	RenderBitmap();
	Invalidate();
	TRACE("invalidate done\n");
}
 
 
void
ScopeView::RenderTrack(BMediaTrack *track, const media_format &format)
{
	fMediaTrack = track;
	fPlayFormat = format;
	release_sem(fRenderSem);
}
 
 
void
ScopeView::CancelRendering()
{
	fIsRendering = false;
}
 
 
void
ScopeView::FrameResized(float width, float height)
{
	InitBitmap();
	RenderBitmap();
	Invalidate();
	TRACE("invalidate done\n");
}
 
 
void
ScopeView::MouseDown(BPoint position)
{
	if (!fMediaTrack)
		return;
 
	uint32 buttons;
	BPoint point;
	GetMouse(&point, &buttons);
 
	if (buttons & B_PRIMARY_MOUSE_BUTTON) {
		// fill the drag message
		BMessage drag(B_SIMPLE_DATA);
		drag.AddInt32("be:actions", B_COPY_TARGET);
		drag.AddString("be:clip_name", "Audio Clip");
		drag.AddString("be:types", B_FILE_MIME_TYPE);
		
		uint8* data;
		size_t size;
		
		BMimeType wavType("audio/x-wav");
		if (wavType.InitCheck() < B_OK
			|| wavType.GetIcon(&data, &size) < B_OK) {
			wavType.SetTo("audio");
			if (wavType.InitCheck() < B_OK
				|| wavType.GetIcon(&data, &size) < B_OK) {
				return;
			}
		}
 
		BBitmap* bitmap = new BBitmap(
			BRect(0, 0, 31, 31), 0, B_RGBA32);
		if (BIconUtils::GetVectorIcon(data, size, bitmap) < B_OK) {
			delete[] data;
			delete bitmap;
			return;
		}
		delete[] data;
		DragMessage(&drag, bitmap, B_OP_ALPHA, BPoint(0,0));
	}
}
 
 
void
ScopeView::InitBitmap()
{
	if (fBitmapView) {
		fBitmap->RemoveChild(fBitmapView);
		delete fBitmapView;
	}
	if (fBitmap)
		delete fBitmap;
		
	BRect rect = Bounds();
	
	fBitmap = new BBitmap(rect, BScreen().ColorSpace(), true);
	memset(fBitmap->Bits(), 0, fBitmap->BitsLength());
	
	rect.OffsetToSelf(B_ORIGIN);
	fBitmapView = new BView(rect.OffsetToSelf(B_ORIGIN), "bitmapView", 
		B_FOLLOW_LEFT|B_FOLLOW_TOP, B_WILL_DRAW);
	fBitmap->AddChild(fBitmapView);
}
 
 
void
ScopeView::RenderBitmap()
{
	if (!fMediaTrack)
		return;
	
	/* rendering */
	fBitmap->Lock();
	memset(fBitmap->Bits(), 0, fBitmap->BitsLength());
	float width = fBitmapView->Bounds().Width() + 1;
	
	fBitmapView->SetDrawingMode(B_OP_ADD);
	fBitmapView->SetHighColor(15,60,15);
	int32 leftIndex = 
		(fTotalTime != 0) ? fLeftTime * 20000 / fTotalTime : 0;
	int32 rightIndex = 
		(fTotalTime != 0) ? fRightTime * 20000 / fTotalTime : 20000;
	
	for (int32 i = leftIndex; i<rightIndex; i++) {
		BPoint point((i - leftIndex) * width / (rightIndex - leftIndex), 
			fPreview[i]);
		//TRACE("point x %f y %f\n", point.x, point.y);
		fBitmapView->StrokeLine(point, point);
	}
	
	fBitmap->Unlock();
}
 

V595 The 'fBitmap' pointer was utilized before it was verified against nullptr. Check lines: 333, 336.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fRenderSem, fPreview.