Added SoundRecorder (inspired by SoundCapture from beos samples)

Alpha state


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@13035 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Jérôme Duval
2005-06-09 20:28:21 +00:00
parent 79a70aa59c
commit 57e2f323c7
32 changed files with 7468 additions and 0 deletions
+1
View File
@@ -14,6 +14,7 @@ SubInclude OBOS_TOP src apps people ;
SubInclude OBOS_TOP src apps poorman ;
SubInclude OBOS_TOP src apps pulse ;
SubInclude OBOS_TOP src apps showimage ;
SubInclude OBOS_TOP src apps soundrecorder ;
SubInclude OBOS_TOP src apps stylededit ;
SubInclude OBOS_TOP src apps terminal ;
SubInclude OBOS_TOP src apps tracker ;
+78
View File
@@ -0,0 +1,78 @@
/*
* 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 <Bitmap.h>
#include <Debug.h>
#include <Screen.h>
#include "DrawingTidbits.h"
rgb_color
ShiftColor(rgb_color color, float percent)
{
rgb_color result = {
ShiftComponent(color.red, percent),
ShiftComponent(color.green, percent),
ShiftComponent(color.blue, percent),
0
};
return result;
}
static bool
CompareColors(const rgb_color a, const rgb_color b)
{
return a.red == b.red
&& a.green == b.green
&& a.blue == b.blue
&& a.alpha == b.alpha;
}
bool
operator==(const rgb_color &a, const rgb_color &b)
{
return CompareColors(a, b);
}
bool
operator!=(const rgb_color &a, const rgb_color &b)
{
return !CompareColors(a, b);
}
void
ReplaceColor(BBitmap *bitmap, rgb_color from, rgb_color to)
{
ASSERT(bitmap->ColorSpace() == B_COLOR_8_BIT); // other color spaces not implemented yet
BScreen screen(B_MAIN_SCREEN_ID);
uint32 fromIndex = screen.IndexForColor(from);
uint32 toIndex = screen.IndexForColor(to);
uchar *bits = (uchar *)bitmap->Bits();
int32 bitsLength = bitmap->BitsLength();
for (int32 index = 0; index < bitsLength; index++)
if (bits[index] == fromIndex)
bits[index] = toIndex;
}
void
ReplaceTransparentColor(BBitmap *bitmap, rgb_color with)
{
ASSERT(bitmap->ColorSpace() == B_COLOR_8_BIT); // other color spaces not implemented yet
BScreen screen(B_MAIN_SCREEN_ID);
uint32 withIndex = screen.IndexForColor(with);
uchar *bits = (uchar *)bitmap->Bits();
int32 bitsLength = bitmap->BitsLength();
for (int32 index = 0; index < bitsLength; index++)
if (bits[index] == B_TRANSPARENT_8_BIT)
bits[index] = withIndex;
}
+50
View File
@@ -0,0 +1,50 @@
/*
* 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)
*/
#ifndef __DRAWING_TIBITS__
#define __DRAWING_TIBITS__
#include <GraphicsDefs.h>
rgb_color ShiftColor(rgb_color , float );
bool operator==(const rgb_color &, const rgb_color &);
bool operator!=(const rgb_color &, const rgb_color &);
inline uchar
ShiftComponent(uchar component, float percent)
{
// change the color by <percent>, make sure we aren't rounding
// off significant bits
if (percent >= 1)
return (uchar)(component * (2 - percent));
else
return (uchar)(255 - percent * (255 - component));
}
inline rgb_color
Color(int32 r, int32 g, int32 b, int32 alpha = 255)
{
rgb_color result;
result.red = r;
result.green = g;
result.blue = b;
result.alpha = alpha;
return result;
}
const rgb_color kWhite = { 255, 255, 255, 255};
const rgb_color kBlack = { 0, 0, 0, 255};
const float kDarkness = 1.06;
const float kDimLevel = 0.6;
void ReplaceColor(BBitmap *bitmap, rgb_color from, rgb_color to);
void ReplaceTransparentColor(BBitmap *bitmap, rgb_color with);
#endif
+100
View File
@@ -0,0 +1,100 @@
/*******************************************************************************
/
/ File: FileUtils.cpp
/
/ Description: Utility functions for copying file data and attributes.
/
/ Copyright 1998-1999, Be Incorporated, All Rights Reserved
/
*******************************************************************************/
#include <stdio.h>
#include <fs_attr.h>
#include "array_delete.h"
#include "FileUtils.h"
status_t CopyFileData(BFile& dst, BFile& src)
{
struct stat src_stat;
status_t err = src.GetStat(&src_stat);
if (err != B_OK) {
printf("couldn't get stat: %#010lx\n", err);
return err;
}
size_t bufSize = src_stat.st_blksize;
if (! bufSize) {
bufSize = 32768;
}
char* buf = new char[bufSize];
array_delete<char> bufDelete(buf);
printf("copy data, bufSize = %ld\n", bufSize);
// copy data
while (true) {
ssize_t bytes = src.Read(buf, bufSize);
if (bytes > 0) {
ssize_t result = dst.Write(buf, bytes);
if (result != bytes) {
printf("result = %#010lx, bytes = %#010lx\n", (uint32) result,
(uint32) bytes);
return B_ERROR;
}
} else {
if (bytes < 0) {
printf(" bytes = %#010lx\n", (uint32) bytes);
return bytes;
} else {
// EOF
break;
}
}
}
// finish up miscellaneous stat stuff
dst.SetPermissions(src_stat.st_mode);
dst.SetOwner(src_stat.st_uid);
dst.SetGroup(src_stat.st_gid);
dst.SetModificationTime(src_stat.st_mtime);
dst.SetCreationTime(src_stat.st_crtime);
return B_OK;
}
status_t CopyAttributes(BNode& dst, BNode& src)
{
// copy attributes
src.RewindAttrs();
char name[B_ATTR_NAME_LENGTH];
while (src.GetNextAttrName(name) == B_OK) {
attr_info info;
if (src.GetAttrInfo(name, &info) == B_OK) {
size_t bufSize = info.size;
char* buf = new char[bufSize];
array_delete<char> bufDelete = buf;
// copy one attribute
ssize_t bytes = src.ReadAttr(name, info.type, 0, buf, bufSize);
if (bytes > 0) {
dst.WriteAttr(name, info.type, 0, buf, bufSize);
} else {
return bytes;
}
}
}
return B_OK;
}
status_t CopyFile(BFile& dst, BFile& src)
{
status_t err = CopyFileData(dst, src);
if (err != B_OK)
return err;
return CopyAttributes(dst, src);
}
+21
View File
@@ -0,0 +1,21 @@
/*******************************************************************************
/
/ File: FileUtils.h
/
/ Description: Utility functions for copying file data and attributes.
/
/ Copyright 1998-1999, Be Incorporated, All Rights Reserved
/
*******************************************************************************/
#if ! defined( _FileUtils_h )
#define _FileUtils_h
#include <File.h>
status_t CopyFile(BFile& dest, BFile& src);
status_t CopyFileData(BFile& dst, BFile& src);
status_t CopyAttributes(BNode& dst, BNode& src);
#endif /* _FileUtils_h */
+19
View File
@@ -0,0 +1,19 @@
SubDir OBOS_TOP src apps soundrecorder ;
App SoundRecorder :
DrawingTidbits.cpp
FileUtils.cpp
RecorderApp.cpp
RecorderWindow.cpp
ScopeView.cpp
SoundConsumer.cpp
SoundListView.cpp
SoundUtils.cpp
TrackSlider.cpp
TransportButton.cpp
UpDownButton.cpp
VUView.cpp
VolumeSlider.cpp
: libbe.so libmedia.so libtracker.so
: SoundRecorder.rdef
;
+37
View File
@@ -0,0 +1,37 @@
/*
* 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 "RecorderApp.h"
#include "RecorderWindow.h"
RecorderApp::RecorderApp(const char * signature) :
BApplication(signature), fRecorderWin(NULL)
{
}
RecorderApp::~RecorderApp()
{
}
void
RecorderApp::ReadyToRun()
{
BApplication::ReadyToRun();
fRecorderWin = new RecorderWindow();
fRecorderWin->Show();
}
int
main()
{
RecorderApp app("application/x-vnd.Haiku-SoundRecorder");
app.Run();
return 0;
}
+26
View File
@@ -0,0 +1,26 @@
/*
* 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)
*/
#ifndef RECORDERAPP_H
#define RECORDERAPP_H
#include <Application.h>
class RecorderWindow;
class RecorderApp : public BApplication {
public:
RecorderApp(const char * signature);
virtual ~RecorderApp();
virtual void ReadyToRun();
private:
RecorderWindow* fRecorderWin;
};
#endif /* RECORDERAPP_H */
File diff suppressed because it is too large Load Diff
+164
View File
@@ -0,0 +1,164 @@
/*
* 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)
*/
#ifndef RECORDERWINDOW_H
#define RECORDERWINDOW_H
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <FilePanel.h>
#include <MediaFile.h>
#include <MediaNode.h>
#include <MediaTrack.h>
#include <SoundPlayer.h>
#include <Window.h>
#include "ScopeView.h"
#include "TransportButton.h"
#include "TrackSlider.h"
#include "UpDownButton.h"
#include "VolumeSlider.h"
#include "VUView.h"
class BMediaRoster;
class BBox;
class BButton;
class BCheckBox;
class BTextControl;
class BMenuField;
class SoundConsumer;
class SoundListView;
class BScrollView;
class BSlider;
class BStringView;
class RecorderWindow : public BWindow {
public:
RecorderWindow();
virtual ~RecorderWindow();
virtual bool QuitRequested();
virtual void MessageReceived(BMessage * message);
enum {
RECORD = 'cw00', // command messages
PLAY,
STOP,
REWIND,
FORWARD,
SAVE,
VIEW_LIST,
INPUT_SELECTED = 'cW00', // control messages
LENGTH_CHANGED,
SOUND_SELECTED,
STOP_PLAYING,
STOP_RECORDING,
RECORD_PERIOD,
PLAY_PERIOD,
UPDATE_TRACKSLIDER,
POSITION_CHANGED
};
void AddSoundItem(const BEntry& entry, bool temp = false);
private:
BMediaRoster * fRoster;
VUView *fVUView;
ScopeView *fScopeView;
RecordButton * fRecordButton;
PlayPauseButton * fPlayButton;
TransportButton * fStopButton;
TransportButton * fRewindButton;
TransportButton * fForwardButton;
TransportButton * fSaveButton;
VolumeSlider *fVolumeSlider;
TrackSlider *fTrackSlider;
UpDownButton * fUpDownButton;
BTextControl * fLengthControl;
BMenuField * fInputField;
SoundConsumer * fRecordNode;
BSoundPlayer * fPlayer;
bool fRecording;
SoundListView * fSoundList;
BDirectory fTempDir;
int fTempCount;
BBox * fBottomBox;
BBox * fFileInfoBox;
BStringView *fFilename;
BStringView *fFormat;
BStringView *fCompression;
BStringView *fChannels;
BStringView *fSampleSize;
BStringView *fSampleRate;
BStringView *fDuration;
enum BtnState {
btnPaused,
btnRecording,
btnPlaying
};
BtnState fButtonState;
BEntry fRecEntry;
BFile fRecFile;
off_t fRecLimit;
off_t fRecSize;
media_node fAudioInputNode;
media_output fAudioOutput;
media_input fRecInput;
BMediaFile *fPlayFile;
media_format fPlayFormat;
BMediaTrack *fPlayTrack;
int64 fPlayLimit;
int64 fPlayFrame;
media_node fAudioMixerNode;
BFilePanel fSavePanel;
status_t InitWindow();
void Record(BMessage * message);
void Play(BMessage * message);
void Stop(BMessage * message);
void Save(BMessage * message);
void DoSave(BMessage * message);
void Input(BMessage * message);
void Length(BMessage * message);
void Selected(BMessage * message);
status_t MakeRecordConnection(const media_node & input);
status_t BreakRecordConnection();
status_t StopRecording();
status_t MakePlayConnection(const media_multi_audio_format & format);
status_t BreakPlayConnection();
status_t StopPlaying();
status_t NewTempName(char * buffer);
void CalcSizes(float min_width, float min_height);
void SetButtonState(BtnState state);
void UpdateButtons();
void UpdatePlayFile();
void ErrorAlert(const char * action, status_t err);
static void RecordFile(void * cookie, bigtime_t timestamp, void * data, size_t size, const media_raw_audio_format & format);
static void NotifyRecordFile(void * cookie, int32 code, ...);
static void PlayFile(void * cookie, void * data, size_t size, const media_raw_audio_format & format);
static void NotifyPlayFile(void * cookie, BSoundPlayer::sound_player_notification code, ...);
void RefsReceived(BMessage *msg);
};
#endif /* RECORDERWINDOW_H */
+271
View File
@@ -0,0 +1,271 @@
/*
* 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 <Screen.h>
#include <Window.h>
#include "DrawingTidbits.h"
#include "ScopeView.h"
ScopeView::ScopeView(BRect rect, uint32 resizeFlags)
: BView(rect, "vumeter", resizeFlags, B_WILL_DRAW | B_FRAME_EVENTS),
fThreadId(-1),
fBitmap(NULL),
fIsRendering(false),
fMediaTrack(NULL),
fQuitting(false),
fMainTime(0),
fRightTime(1000000),
fLeftTime(0),
fTotalTime(1000000)
{
fBitmap = new BBitmap(rect, BScreen().ColorSpace(), true);
memset(fBitmap->Bits(), 0, fBitmap->BitsLength());
rect.OffsetToSelf(B_ORIGIN);
rect.right -= 2;
fBitmapView = new BView(rect.OffsetToSelf(B_ORIGIN), "bitmapView", B_FOLLOW_LEFT|B_FOLLOW_TOP, B_WILL_DRAW);
fBitmap->AddChild(fBitmapView);
fRenderSem = create_sem(0, "scope rendering");
fHeight = Bounds().Height();
}
ScopeView::~ScopeView()
{
delete_sem(fRenderSem);
}
void
ScopeView::AttachedToWindow()
{
SetViewColor(B_TRANSPARENT_COLOR);
Run();
}
void
ScopeView::DetachedFromWindow()
{
Quit();
}
void
ScopeView::Draw(BRect updateRect)
{
BRect bounds = Bounds();
if (!fIsRendering)
DrawBitmapAsync(fBitmap, BPoint(2,0));
float x = 2;
if (fTotalTime !=0)
x = 2 + (fMainTime - fLeftTime) * (bounds.right - 2) / (fRightTime - fLeftTime);
SetHighColor(60,255,40);
StrokeLine(BPoint(x, bounds.top), BPoint(x, bounds.bottom));
Sync();
}
void
ScopeView::Run()
{
fThreadId = spawn_thread(&RenderLaunch, "Scope view", B_NORMAL_PRIORITY, this);
if (fThreadId < 0)
return;
resume_thread(fThreadId);
}
void
ScopeView::Quit()
{
delete_sem(fRenderSem);
fQuitting = true;
snooze(10000);
kill_thread(fThreadId);
}
int32
ScopeView::RenderLaunch(void *data)
{
ScopeView *scope = (ScopeView*) data;
scope->RenderLoop();
return B_OK;
}
void
ScopeView::RenderLoop()
{
while (!fQuitting) {
if (acquire_sem(fRenderSem)!=B_OK)
continue;
fIsRendering = true;
int32 frame_size = (fPlayFormat.u.raw_audio.format & 0xf) * fPlayFormat.u.raw_audio.channel_count;
int64 totalFrames = fMediaTrack->CountFrames();
int16 samples[fPlayFormat.u.raw_audio.buffer_size / (fPlayFormat.u.raw_audio.format & 0xf)];
int64 frames = 0;
int64 sum = 0;
int64 framesIndex = 0;
int32 sumCount = 0;
fMediaTrack->SeekToFrame(&frames);
printf("begin computing\n");
int32 previewIndex = 0;
while (fMediaTrack->ReadFrames(samples, &frames) == B_OK) {
//printf("reading block\n");
framesIndex = 0;
while (framesIndex < frames) {
for (; framesIndex < frames && sumCount < totalFrames/20000; framesIndex++, sumCount++) {
sum += samples[2*framesIndex];
sum += samples[2*framesIndex+1];
}
if (previewIndex >= 20000) {
break;
}
if (sumCount >= totalFrames/20000) {
//printf("computing block %ld, sumCount %ld\n", previewIndex, sumCount);
fPreview[previewIndex++] = (int32)(sum / 2 /(totalFrames/20000) / 32767.0 * fHeight / 2 + fHeight / 2);
sumCount = 0;
sum = 0;
}
}
}
printf("finished computing\n");
/* rendering */
RenderBitmap();
/* ask drawing */
fIsRendering = false;
if (Window()->LockWithTimeout(5000) == B_OK) {
Invalidate();
Window()->Unlock();
}
}
}
void
ScopeView::SetMainTime(bigtime_t timestamp)
{
fMainTime = timestamp;
Invalidate();
}
void
ScopeView::SetTotalTime(bigtime_t timestamp)
{
fTotalTime = timestamp;
Invalidate();
}
void
ScopeView::SetLeftTime(bigtime_t timestamp)
{
fLeftTime = timestamp;
RenderBitmap();
Invalidate();
}
void
ScopeView::SetRightTime(bigtime_t timestamp)
{
fRightTime = timestamp;
RenderBitmap();
Invalidate();
}
void
ScopeView::RenderTrack(BMediaTrack *track, media_format format)
{
fMediaTrack = track;
fPlayFormat = format;
release_sem(fRenderSem);
}
void
ScopeView::FrameResized(float width, float height)
{
InitBitmap();
RenderBitmap();
Invalidate();
}
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);
rect.right -= 2;
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();
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]);
//printf("point x %f y %f\n", point.x, point.y);
fBitmapView->StrokeLine(point, point);
}
fBitmap->Unlock();
}
+55
View File
@@ -0,0 +1,55 @@
/*
* 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)
*/
#ifndef SCOPEVIEW_H
#define SCOPEVIEW_H
#include <Bitmap.h>
#include <View.h>
#include <MediaTrack.h>
class ScopeView : public BView
{
public:
ScopeView(BRect rect, uint32 resizeFlags);
~ScopeView();
void AttachedToWindow();
void DetachedFromWindow();
void Draw(BRect updateRect);
void SetMainTime(bigtime_t timestamp);
void SetLeftTime(bigtime_t timestamp);
void SetRightTime(bigtime_t timestamp);
void SetTotalTime(bigtime_t timestamp);
void RenderTrack(BMediaTrack *track, media_format format);
virtual void FrameResized(float width, float height);
private:
void Run();
void Quit();
static int32 RenderLaunch(void *data);
void RenderLoop();
void RenderBitmap();
void InitBitmap();
thread_id fThreadId;
BBitmap *fBitmap;
BView *fBitmapView;
sem_id fRenderSem;
bool fIsRendering;
BMediaTrack *fMediaTrack;
media_format fPlayFormat;
bool fQuitting;
bigtime_t fMainTime;
bigtime_t fRightTime;
bigtime_t fLeftTime;
bigtime_t fTotalTime;
int32 fPreview[20000];
float fHeight;
};
#endif /* SCOPEVIEW_H */
+706
View File
@@ -0,0 +1,706 @@
/*******************************************************************************
/
/ File: SoundConsumer.cpp
/
/ Description: Record sound from some sound-producing Node.
/
/ Copyright 1998-1999, Be Incorporated, All Rights Reserved
/
*******************************************************************************/
#include <stdio.h>
#include <OS.h>
#include <scheduler.h>
#include <Buffer.h>
#include <TimeSource.h>
#include "SoundPrivate.h"
#include "SoundConsumer.h"
#include "array_delete.h"
// If we don't mind the format changing to another format while
// running, we can define this to 1. Look for the symbol down in the source.
#define ACCEPT_ANY_FORMAT_CHANGE 0
// Compiling with NDEBUG means "release" -- it also turns off assert() and
// other such debugging aids. By contrast, DEBUG turns on extra debugging
// in some programs.
#if !NDEBUG
#define FPRINTF fprintf
#else
#define FPRINTF (void)
#endif
// Comment out the FPRINTF part of these lines to reduce verbiage.
// Enabling MESSAGE will kill performance on slower machines, because it
// prints for each message received (including each buffer).
#define NODE //FPRINTF
#define MESSAGE //FPRINTF
SoundConsumer::SoundConsumer(
const char * name,
SoundProcessFunc recordFunc,
SoundNotifyFunc notifyFunc,
void * cookie) :
BMediaNode(name ? name : "SoundConsumer"),
BBufferConsumer(B_MEDIA_RAW_AUDIO)
{
NODE(stderr, "SoundConsumer::SoundConsumer(%p, %p, %p, %p)\n",
name, recordFunc, notifyFunc, cookie);
if (!name) name = "SoundConsumer";
// Set up the hook functions.
m_recordHook = recordFunc;
m_notifyHook = notifyFunc;
m_cookie = cookie;
// Create the port that we publish as our Control Port.
char pname[32];
sprintf(pname, "%.20s Control", name);
m_port = create_port(10, pname);
// Initialize our single media_input. Make sure it knows
// the Control Port associated with the destination, and
// the index of the destination (since we only have one,
// that's trivial).
m_input.destination.port = m_port;
m_input.destination.id = 1;
sprintf(m_input.name, "%.20s Input", name);
// Set up the timing variables that we'll be using.
m_trTimeout = 0LL;
m_tpSeekAt = 0;
m_tmSeekTo = 0;
m_delta = 0;
m_seeking = false;
// Create, and run, the thread that we use to service
// the Control Port.
sprintf(pname, "%.20s Service", name);
m_thread = spawn_thread(ThreadEntry, pname, 110, this);
resume_thread(m_thread);
}
SoundConsumer::~SoundConsumer()
{
NODE(stderr, "SoundConsumer::~SoundConsumer()\n");
// Signal to our thread that it's time to go home.
write_port(m_port, MSG_QUIT_NOW, 0, 0);
status_t s;
while (wait_for_thread(m_thread, &s) == B_INTERRUPTED)
NODE(stderr, "wait_for_thread() B_INTERRUPTED\n");
delete_port(m_port);
}
status_t
SoundConsumer::SetHooks(
SoundProcessFunc recordFunc,
SoundNotifyFunc notifyFunc,
void * cookie)
{
// SetHooks needs to be synchronized with the service thread, else we may
// call the wrong hook function with the wrong cookie, which would be bad.
// Rather than do locking, which is expensive, we streamline the process
// by sending our service thread a request to change the hooks, and waiting
// for the acknowledge.
status_t err = B_OK;
set_hooks_q cmd;
cmd.process = recordFunc;
cmd.notify = notifyFunc;
cmd.cookie = cookie;
// If we're not in the service thread, we need to round-trip a message.
if (find_thread(0) != m_thread) {
cmd.reply = create_port(1, "SetHooks reply");
// Send the private message to our service thread.
err = write_port(ControlPort(), MSG_CHANGE_HOOKS, &cmd, sizeof(cmd));
if (err >= 0) {
int32 code;
// Wait for acknowledge from the service thread.
err = read_port_etc(cmd.reply, &code, 0, 0, B_TIMEOUT, 6000000LL);
if (err > 0) err = 0;
NODE(stderr, "SoundConsumer::SetHooks read reply: %#010lx\n", err);
}
// Clean up.
delete_port(cmd.reply);
}
else {
// Within the service thread, it's OK to just go ahead and do the change.
DoHookChange(&cmd);
}
return err;
}
////////////////////////////////////////////////////////////////////////////////
//
// BMediaNode-derived methods
//
////////////////////////////////////////////////////////////////////////////////
port_id SoundConsumer::ControlPort() const
{
return m_port;
}
BMediaAddOn* SoundConsumer::AddOn(
int32 * internal_id) const
{
// This object is instantiated inside an application.
// Therefore, it has no add-on.
if (internal_id) *internal_id = 0;
return 0;
}
void SoundConsumer::Start(
bigtime_t performance_time)
{
// Since we are a consumer and just blindly accept buffers that are
// thrown at us, we don't need to do anything special in Start()/Stop().
// If we were (also) a producer, we'd have to be more elaborate.
// The only thing we do is immediately perform any queued Seek based on
// the start time, which is the right thing to do (seeing as we were
// Seek()-ed when we weren't started).
if (m_seeking) {
m_delta = performance_time - m_tmSeekTo;
m_seeking = false;
}
if (m_notifyHook) {
(*m_notifyHook)(m_cookie, B_WILL_START, performance_time);
}
else {
Notify(B_WILL_START, performance_time);
}
}
void SoundConsumer::Stop(
bigtime_t performance_time,
bool immediate)
{
// Since we are a consumer and just blindly accept buffers that are
// thrown at us, we don't need to do anything special in Start()/Stop().
// If we were (also) a producer, we'd have to be more elaborate.
// Note that this is not strictly in conformance with The Rules,
// but since this is not an add-on Node for use with any application;
// it's a Node over which we have complete control, we can live with
// treating buffers received before the start time or after the stop
// time as any other buffer.
if (m_notifyHook) {
(*m_notifyHook)(m_cookie, B_WILL_STOP, performance_time, immediate);
}
else {
Notify(B_WILL_STOP, performance_time, immediate);
}
}
void SoundConsumer::Seek(
bigtime_t media_time,
bigtime_t performance_time)
{
// Seek() on a consumer just serves to offset the time stamp
// of received buffers passed to our Record hook function.
// In the hook function, you may wish to save those time stamps
// to disk or otherwise store them. You may also want to
// synchronize this node's media time with an upstream
// producer's media time to make this offset meaningful.
if (m_notifyHook) {
(*m_notifyHook)(m_cookie, B_WILL_SEEK, performance_time, media_time);
}
else {
Notify(B_WILL_SEEK, performance_time, media_time);
}
m_tpSeekAt = performance_time;
m_tmSeekTo = media_time;
m_seeking = true;
}
void SoundConsumer::SetRunMode(
run_mode mode)
{
if (mode == BMediaNode::B_OFFLINE) {
// BMediaNode::B_OFFLINE means we don't need to run in
// real time. So, we shouldn't run as a real time
// thread.
int32 new_prio = suggest_thread_priority(B_OFFLINE_PROCESSING);
set_thread_priority(m_thread, new_prio);
}
else {
// We're running in real time, so we'd better have
// a big enough thread priority to handle it!
// Here's where those magic scheduler values
// come from:
//
// * In the worst case, we process one buffer per
// reschedule (we get rescheduled when we go to
// look for a message on our Control Port), so
// in order to keep up with the incoming buffers,
// the duration of one buffer becomes our
// scheduling period. If we don't know anything
// about the buffers, we pick a reasonable
// default.
// * We're a simple consumer, so we don't have to
// be too picky about the jitter. Half a period
// of jitter means that we'd have to get two
// consecutive worst-case reschedules before
// we'd fall behind.
// * The amount of time we spend processing is
// our ProcessingLatency().
bigtime_t period = 10000;
if (buffer_duration(m_input.format.u.raw_audio) > 0) {
period = buffer_duration(m_input.format.u.raw_audio);
}
// assuming we're running for 500 us or less per buffer
int32 new_prio = suggest_thread_priority(B_AUDIO_RECORDING,
period, period/2, ProcessingLatency());
set_thread_priority(m_thread, new_prio);
}
}
void SoundConsumer::TimeWarp(
bigtime_t at_real_time,
bigtime_t to_performance_time)
{
// Since buffers will come pre-time-stamped, we only need to look
// at them, so we can ignore the time warp as a consumer.
if (m_notifyHook) {
(*m_notifyHook)(m_cookie, B_WILL_TIMEWARP, at_real_time, to_performance_time);
}
else {
Notify(B_WILL_TIMEWARP, at_real_time, to_performance_time);
}
}
void SoundConsumer::Preroll()
{
// There is nothing for us to do in Preroll()
}
void SoundConsumer::SetTimeSource(
BTimeSource * /* time_source */)
{
// We don't need to do anything special to take note of the
// fact that the time source changed, because we get our timing
// information from the buffers we receive.
}
status_t SoundConsumer::HandleMessage(
int32 message,
const void * data,
size_t size)
{
// Check with each of our superclasses to see if they
// understand the message. If none of them do, call
// BMediaNode::HandleBadMessage().
if ((BBufferConsumer::HandleMessage(message, data, size) < 0)
&& (BMediaNode::HandleMessage(message, data, size) < 0))
{
HandleBadMessage(message, data, size);
return B_ERROR;
}
return B_OK;
}
////////////////////////////////////////////////////////////////////////////////
//
// BBufferConsumer-derived methods
//
////////////////////////////////////////////////////////////////////////////////
status_t SoundConsumer::AcceptFormat(
const media_destination & dest,
media_format * format)
{
// We only accept formats aimed at our single input.
if (dest != m_input.destination) {
return B_MEDIA_BAD_DESTINATION;
}
// If no format is specified, we say we want raw audio.
if (format->type <= 0) {
format->type = B_MEDIA_RAW_AUDIO;
format->u.raw_audio = media_raw_audio_format::wildcard;
}
// If a non-raw-audio format is specified, we tell the world what
// we want, and that the specified format was unacceptable to us.
else if (format->type != B_MEDIA_RAW_AUDIO) {
format->type = B_MEDIA_RAW_AUDIO;
format->u.raw_audio = media_raw_audio_format::wildcard;
return B_MEDIA_BAD_FORMAT;
}
#if !ACCEPT_ANY_FORMAT_CHANGE
// If we're already connected, and this format doesn't go with the
// format in effect, we dont' accept this new format.
if (!format_is_compatible(*format, m_input.format)) {
*format = m_input.format;
return B_MEDIA_BAD_FORMAT;
}
#endif
// I guess we're OK by now, because we have no particular needs as
// far as frame rate, sample format, etc go.
return B_OK;
}
status_t SoundConsumer::GetNextInput(
int32 * cookie,
media_input * out_input)
{
NODE(stderr, "SoundConsumer: GetNextInput()\n");
// The "next" is kind of misleading, since it's also used for
// getting the first (and only) input.
if (!*cookie) {
if (m_input.source == media_source::null) {
// If there's no current connection, make sure we return a
// reasonable format telling the world we accept any raw audio.
m_input.format.type = B_MEDIA_RAW_AUDIO;
m_input.format.u.raw_audio = media_raw_audio_format::wildcard;
m_input.node = Node();
m_input.destination.port = ControlPort();
m_input.destination.id = 1;
}
*out_input = m_input;
*cookie = 1;
return B_OK;
}
// There's only one input.
return B_BAD_INDEX;
}
void SoundConsumer::DisposeInputCookie(
int32 /* cookie */)
{
// We didn't allocate any memory or set any state in GetNextInput()
// so this function is a no-op.
}
void SoundConsumer::BufferReceived(
BBuffer * buffer)
{
NODE(stderr, "SoundConsumer::BufferReceived()\n");
// Whee, a buffer! Update the seek info, if necessary.
if (m_seeking && buffer->Header()->start_time >= m_tpSeekAt) {
m_delta = m_tpSeekAt - m_tmSeekTo;
m_seeking = false;
}
// If there is a record hook, let the interested party have at it!
if (m_recordHook) {
(*m_recordHook)(m_cookie, buffer->Header()->start_time-m_delta, buffer->Data(), buffer->Header()->size_used, m_input.format.u.raw_audio);
}
else {
Record(buffer->Header()->start_time-m_delta, buffer->Data(), buffer->Header()->size_used, m_input.format.u.raw_audio);
}
// Buffers should ALWAYS be recycled, else whomever is producing them
// will starve.
buffer->Recycle();
}
void SoundConsumer::ProducerDataStatus(
const media_destination & for_whom,
int32 status,
bigtime_t at_media_time)
{
if (for_whom == m_input.destination) {
// Tell whomever is interested that the upstream producer will or won't
// send more data in the immediate future.
if (m_notifyHook) {
(*m_notifyHook)(m_cookie, B_PRODUCER_DATA_STATUS, status, at_media_time);
}
else {
Notify(B_PRODUCER_DATA_STATUS, status, at_media_time);
}
}
}
status_t SoundConsumer::GetLatencyFor(
const media_destination & for_whom,
bigtime_t * out_latency,
media_node_id * out_timesource)
{
// We only accept requests for the one-and-only input of our Node.
if (for_whom != m_input.destination) {
return B_MEDIA_BAD_DESTINATION;
}
// Tell the world about our latency information (overridable by user).
*out_latency = TotalLatency();
*out_timesource = TimeSource()->Node().node;
return B_OK;
}
status_t SoundConsumer::Connected(
const media_source & producer,
const media_destination & where,
const media_format & with_format,
media_input * out_input)
{
NODE(stderr, "SoundConsumer::Connected()\n");
// Only accept connection requests when we're not already connected.
if (m_input.source != media_source::null) {
return B_MEDIA_BAD_DESTINATION;
}
// Only accept connection requests on the one-and-only available input.
if (where != m_input.destination) {
return B_MEDIA_BAD_DESTINATION;
}
// Other than that, we accept pretty much anything. The format has been
// pre-cleared through AcceptFormat(), and we accept any format anyway.
m_input.source = producer;
m_input.format = with_format;
// Tell whomever is interested that there's now a connection.
if (m_notifyHook) {
(*m_notifyHook)(m_cookie, B_CONNECTED, m_input.name);
}
else {
Notify(B_CONNECTED, m_input.name);
}
// This is the most important line -- return our connection information
// to the world so it can use it!
*out_input = m_input;
return B_OK;
}
void SoundConsumer::Disconnected(
const media_source & producer,
const media_destination & where)
{
// We can't disconnect something which isn't us.
if (where != m_input.destination) {
return;
}
// We can't disconnect from someone who isn't connected to us.
if (producer != m_input.source) {
return;
}
// Tell the interested party that it's time to leave.
if (m_notifyHook) {
(*m_notifyHook)(m_cookie, B_DISCONNECTED);
}
else {
Notify(B_DISCONNECTED);
}
// Mark ourselves as not-connected.
m_input.source = media_source::null;
}
status_t SoundConsumer::FormatChanged(
const media_source & producer,
const media_destination & consumer,
int32 from_change_count,
const media_format & format)
{
NODE(stderr, "SoundConsumer::Connected()\n");
// The up-stream guy feels like changing the format. If we can accept
// arbitrary format changes, we just say "OK". If, however, we're recording
// to a file, that's not such a good idea; we only accept format changes
// that are compatible with the format we're already using. You set this
// behaviour at compile time by defining ACCEPT_ANY_FORMAT_CHANGE to 1 or 0.
status_t err = B_OK;
#if ACCEPT_ANY_FORMAT_CHANGE
media_format fmt(format);
err = AcceptFormat(m_input.destination, &fmt);
#else
if (m_input.source != media_source::null) {
err = format_is_compatible(format, m_input.format) ? B_OK : B_MEDIA_BAD_FORMAT;
}
#endif
if (err >= B_OK) {
m_input.format = format;
if (m_notifyHook) {
(*m_notifyHook)(m_cookie, B_FORMAT_CHANGED, &m_input.format.u.raw_audio);
}
else {
Notify(B_FORMAT_CHANGED, &m_input.format.u.raw_audio);
}
}
return err;
}
void
SoundConsumer::DoHookChange(
void * msg)
{
// Tell the old guy we're changing the hooks ...
if (m_notifyHook) {
(*m_notifyHook)(m_cookie, B_HOOKS_CHANGED);
}
else {
Notify(B_HOOKS_CHANGED);
}
// ... and then do it.
set_hooks_q * ptr = (set_hooks_q *)msg;
m_recordHook = ptr->process;
m_notifyHook = ptr->notify;
m_cookie = ptr->cookie;
}
status_t
SoundConsumer::ThreadEntry(
void * obj)
{
((SoundConsumer *)obj)->ServiceThread();
return 0;
}
void
SoundConsumer::ServiceThread()
{
// The Big Bad ServiceThread receives messages aimed at this
// Node and dispatches them (typically to HandleMessage()).
// If we were a Producer, we might have to do finicky timing and
// queued Start()/Stop() processing in here. But we ain't.
// A media kit message will never be bigger than B_MEDIA_MESSAGE_SIZE.
// Avoid wasing stack space by dynamically allocating at start.
char * msg = new char[B_MEDIA_MESSAGE_SIZE];
// Make sure we clean up this data when we exit the function.
array_delete<char> msg_delete(msg);
int bad = 0;
while (true) {
// Call read_port_etc() with a timeout derived from a virtual function,
// to allow clients to do special processing if necessary.
bigtime_t timeout = Timeout();
int32 code = 0;
status_t err = read_port_etc(m_port, &code, msg, B_MEDIA_MESSAGE_SIZE,
B_TIMEOUT, timeout);
MESSAGE(stderr, "SoundConsumer::ServiceThread() port %ld message %#010lx\n", m_port, code);
// If we received a message, err will be the size of the message (including 0).
if (err >= 0) {
// Real messages reset the timeout time.
m_trTimeout = 0;
bad = 0;
// Check for our private stop message.
if (code == MSG_QUIT_NOW) {
if (m_notifyHook) {
(*m_notifyHook)(m_cookie, B_NODE_DIES, 0);
}
else {
Notify(B_NODE_DIES, 0);
}
break;
}
// Else check for our private change-hooks message.
else if (code == MSG_CHANGE_HOOKS) {
DoHookChange(msg);
// Write acknowledge to waiting thread.
write_port(((set_hooks_q *)msg)->reply, 0, 0, 0);
}
// Else it has to be a regular media kit message; go ahead and
// dispatch it.
else {
HandleMessage(code, msg, err);
}
}
// Timing out means that there was no buffer. Tell the interested party.
else if (err == B_TIMED_OUT) {
if (m_notifyHook) {
(*m_notifyHook)(m_cookie, B_OP_TIMED_OUT, timeout);
}
else {
Notify(B_OP_TIMED_OUT, timeout);
}
}
// Other errors are bad.
else {
FPRINTF(stderr, "SoundConsumer: error %#010lx\n", err);
bad++;
// If we receive three bad reads with no good messages inbetween,
// things are probably not going to improve (like the port disappeared
// or something) so we call it a day.
if (bad > 3) {
if (m_notifyHook) {
(*m_notifyHook)(m_cookie, B_NODE_DIES, bad, err, code, msg);
}
else {
Notify(B_NODE_DIES, bad, err, code, msg);
}
break;
}
}
}
}
bigtime_t
SoundConsumer::Timeout()
{
// Timeout() is called for each call to read_port_etc() in the service
// thread to figure out a reasonable time-out value. The default behaviour
// we've picked is to exponentially back off from one second and upwards.
// While it's true that 44 back-offs will run us out of precision in a
// bigtime_t, the time to actually reach 44 consecutive back-offs is longer
// than the expected market longevity of just about any piece of real estate.
// Is that the sound of an impending year-fifteen-million software problem? :-)
m_trTimeout = (m_trTimeout < 1000000) ? 1000000 : m_trTimeout*2;
return m_trTimeout;
}
bigtime_t
SoundConsumer::ProcessingLatency()
{
// We're saying it takes us 500 us to process each buffer. If all we do is
// copy the data, it probably takes much less than that, but it doesn't
// hurt to be slightly conservative.
return 500LL;
}
bigtime_t
SoundConsumer::TotalLatency()
{
// Had we been a producer that passes buffers on, we'd have to
// include downstream latency in this value. But we are not.
return ProcessingLatency();
}
void
SoundConsumer::Record(
bigtime_t /* time */,
const void * /* data */,
size_t /* size */,
const media_raw_audio_format & /* format */)
{
// If there is no record hook installed, we instead call this function
// for received buffers.
}
void
SoundConsumer::Notify(
int32 /* cause */,
...)
{
// If there is no notification hook installed, we instead call this function
// for giving notification of various events.
}
+173
View File
@@ -0,0 +1,173 @@
/*******************************************************************************
/
/ File: SoundConsumer.h
/
/ Description: Record sound from some sound-producing Node.
/
/ Copyright 1998, Be Incorporated, All Rights Reserved
/
*******************************************************************************/
#if !defined( _SoundConsumer_h )
#define _SoundConsumer_h
#include <BufferConsumer.h>
#include "SoundUtils.h"
// To use this Consumer:
// 1. Create Record and Notify hooks, or subclass SoundConsumer
// if you'd rather use the inheritance hierarchy.
// * The Record function should do whatever you want to do
// when you receive a buffer.
// * The Notify function should handle whatever events
// you wish to handle (defined in SoundUtil.h).
// 2: Create an instance of SoundConsumer, giving it the
// appropriate hook functions. Or, create an instance of an
// appropriate subclass if you've made one.
// 3: Register your new Consumer with the MediaRoster.
// 4: Connect your Consumer to some Producer.
// 5: Start or Stop the Consumer if your hook functions
// implement behavior for these kinds of events.
// Seek the Consumer to set the offset of the timestamps that
// your Record function will see.
// 6: When you're done, disconnect the Consumer, then delete it.
class SoundConsumer :
public BBufferConsumer
{
public:
SoundConsumer(
const char * name,
SoundProcessFunc recordFunc = NULL,
SoundNotifyFunc notifyFunc = NULL,
void * cookie = NULL);
~SoundConsumer();
// This function is OK to call from any thread.
status_t SetHooks(
SoundProcessFunc recordFunc = NULL,
SoundNotifyFunc notifyFunc = NULL,
void * cookie = NULL);
// The MediaNode interface
public:
virtual port_id ControlPort() const;
virtual BMediaAddOn* AddOn(
int32 * internal_id) const; /* Who instantiated you -- or NULL for app class */
protected:
virtual void Start(
bigtime_t performance_time);
virtual void Stop(
bigtime_t performance_time,
bool immediate);
virtual void Seek(
bigtime_t media_time,
bigtime_t performance_time);
virtual void SetRunMode(
run_mode mode);
virtual void TimeWarp(
bigtime_t at_real_time,
bigtime_t to_performance_time);
virtual void Preroll();
virtual void SetTimeSource(
BTimeSource * time_source);
virtual status_t HandleMessage(
int32 message,
const void * data,
size_t size);
// The BufferConsumer interface
virtual status_t AcceptFormat(
const media_destination & dest,
media_format * format);
virtual status_t GetNextInput( /* cookie starts at 0 */
int32 * cookie,
media_input * out_input);
virtual void DisposeInputCookie(
int32 cookie);
virtual void BufferReceived(
BBuffer * buffer);
virtual void ProducerDataStatus(
const media_destination & for_whom,
int32 status,
bigtime_t at_media_time);
virtual status_t GetLatencyFor(
const media_destination & for_whom,
bigtime_t * out_latency,
media_node_id * out_timesource);
virtual status_t Connected(
const media_source & producer,
const media_destination & where,
const media_format & with_format,
media_input * out_input);
virtual void Disconnected(
const media_source & producer,
const media_destination & where);
virtual status_t FormatChanged(
const media_source & producer,
const media_destination & consumer,
int32 from_change_count,
const media_format & format);
protected:
// Functions called when no hooks are installed.
// OK to override instead of installing hooks.
virtual void Record(
bigtime_t time,
const void * data,
size_t size,
const media_raw_audio_format & format);
virtual void Notify(
int32 cause,
...);
private:
SoundProcessFunc m_recordHook;
SoundNotifyFunc m_notifyHook;
void * m_cookie;
media_input m_input;
thread_id m_thread;
port_id m_port;
// The times we need to deal with
// My notation for times: tr = real time,
// tp = performance time, tm = media time.
bigtime_t m_trTimeout; // how long to wait on the input port
bigtime_t m_tpSeekAt; // when we Seek
bigtime_t m_tmSeekTo; // target time for Seek
// The transformation from media to peformance time.
// d = p - m, so m + d = p.
// Media time is generally governed by the Seek
// function. In our node, we simply use media time as
// the time that we report to the record hook function.
// If we were a producer node, we might use media time
// to track where we were in playing a certain piece
// of media. But we aren't.
bigtime_t m_delta;
// State variables
bool m_seeking; // a Seek is pending
// Functions to calculate timing values. OK to override.
// ProcessingLatency is the time it takes to process a buffer;
// TotalLatency is returned to producer; Timeout is passed
// to call to read_port_etc() in service thread loop.
virtual bigtime_t Timeout();
virtual bigtime_t ProcessingLatency();
virtual bigtime_t TotalLatency();
// The actual thread doing the work
static status_t ThreadEntry(
void * obj);
void ServiceThread();
void DoHookChange(
void * msg);
};
#endif /* _SoundConsumer_h */
+70
View File
@@ -0,0 +1,70 @@
/*
* 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 <Entry.h>
#include "SoundListView.h"
SoundListView::SoundListView(
const BRect & area,
const char * name,
uint32 resize) :
BListView(area, name, B_SINGLE_SELECTION_LIST, resize)
{
}
SoundListView::~SoundListView()
{
}
void
SoundListView::Draw(BRect updateRect)
{
if (IsEmpty()) {
SetHighColor(235,235,235);
FillRect(Bounds());
SetHighColor(0,0,0);
BFont font(be_bold_font);
font.SetSize(12.0);
SetFont(&font);
font_height height;
font.GetHeight(&height);
float width = font.StringWidth("Drop Files Here");
BPoint pt;
pt.x = (Bounds().Width() - width) / 2;
pt.y = (Bounds().Height() + height.ascent + height.descent)/ 2;
DrawString("Drop Files Here", pt);
}
BListView::Draw(updateRect);
}
void
SoundListView::AttachedToWindow()
{
BListView::AttachedToWindow();
SetViewColor(255,255,255);
}
SoundListItem::SoundListItem(
const BEntry & entry,
bool isTemp)
: BStringItem(""),
fEntry(entry),
fIsTemp(isTemp)
{
char name[256];
fEntry.GetName(name);
SetText(name);
}
SoundListItem::~SoundListItem()
{
}
+40
View File
@@ -0,0 +1,40 @@
/*
* 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)
*/
#ifndef SOUNDLISTVIEW_H
#define SOUNDLISTVIEW_H
#include <ListView.h>
class SoundListView : public BListView {
public:
SoundListView(const BRect & area, const char * name, uint32 resize);
virtual ~SoundListView();
virtual void Draw(BRect updateRect);
virtual void AttachedToWindow();
};
#include <ListItem.h>
class SoundListItem : public BStringItem {
public:
SoundListItem(const BEntry & entry, bool isTemp);
virtual ~SoundListItem();
BEntry & Entry() { return fEntry; }
bool IsTemp() { return fIsTemp; }
void SetTemp(bool isTemp) { fIsTemp = isTemp; }
private:
BEntry fEntry;
bool fIsTemp;
};
#endif /* SOUNDLISTVIEW_H */
+41
View File
@@ -0,0 +1,41 @@
/*******************************************************************************
/
/ File: SoundPrivate.h
/
/ Description: Implementation headers for SoundConsumer and SoundProducer.
/
/ Copyright 1998-1999, Be Incorporated, All Rights Reserved
/
*******************************************************************************/
#if ! defined( _SoundPrivate_h )
#define _SoundPrivate_h
// The following are implementation details that we don't
// want to expose to the world at large.
#include "SoundUtils.h"
// This structure is the body of a request that we use to
// implement SetHooks().
struct set_hooks_q {
port_id reply;
void * cookie;
SoundProcessFunc process;
SoundNotifyFunc notify;
};
// All incoming buffers and Media Kit requests arrive at a
// media node in the form of messages (which are generally
// dispatched for you by your superclasses' HandleMessage
// implementations). Each message has a 'type' which is
// analagous to a BMessage's 'what' field. We'll define our
// own private message types for our SoundConsumer and
// SoundProducer to use. The BeOS reserves a range,
// 0x60000000 to 0x7fffffff, for us to use.
enum {
MSG_QUIT_NOW = 0x60000000L,
MSG_CHANGE_HOOKS
};
#endif /* _SoundPrivate_h */
+83
View File
@@ -0,0 +1,83 @@
resource app_signature "application/x-vnd.haiku.SoundRecorder";
resource app_flags B_SINGLE_LAUNCH;
resource app_version
{
major = 1,
middle = 0,
minor = 0,
variety = B_APPV_BETA,
internal = 0,
short_info = "SoundRecorder",
long_info = "Haiku Sound Recorder"
};
resource file_types message
{
"types" = "audio/basic",
"types" = "audio/x-wav",
"types" = "audio/x-riff",
"types" = "audio/x-aifc",
"types" = "audio/x-aiff",
"types" = "audio/wav",
"types" = "audio/aiff",
"types" = "audio/riff"
};
resource large_icon array
{
$"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
$"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
$"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000FFFFFFFFFFFFFFFFFFFFFFFFFF"
$"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FED8D80202FFFFFFFFFFFFFFFFFFFFFF"
$"FFFFFFFFFFFFFFFFFFFFFFFFFFFF00FED88484D8D80202FFFFFFFFFFFFFFFFFF"
$"FFFFFFFFFFFFFFFFFFFFFFFFFF00FED884D8D88484D8D80202FFFFFFFFFFFFFF"
$"FFFFFFFFFFFFFFFFFFFFFFFF00FED8D8D88484D8D8AAAAD8D80202FFFFFFFFFF"
$"FFFFFFFFFFFFFFFFFFFFFF00FED8D88484D8D8AAAAD8D8AA84D8D80202FFFFFF"
$"FFFFFFFFFFFFFFFFFFFF00FED8D884D8D8AAAAD8D8AAAAD8D88484D8D802FFFF"
$"FFFFFFFFFFFFFFFFFF00FE07D8D8D88484D8D8AAAAD8D8AA84D8D884D829FFFF"
$"FFFFFFFFFFFFFFFF00FE070A0707D8D8D8AAAAD8D8AAAAD8D88484D8D804FFFF"
$"FFFFFFFFFFFFFF00FE070A0A0A0A0707D8D8D8AAAAD8D88484D8D8D88329FFFF"
$"FFFFFFFFFFFF00FE070A0A0A0A0A0A0A0707D8D8D88484D8D8D8D8838302FFFF"
$"FFFFFFFFFF00FE070A0A0A0F150A0A0A0A0A0707D8D8D884D8D883838302FFFF"
$"FFFFFFFF00FE070A0A0A0A0F0F0A0A0A0A0A0A0A0707D8D8D8838383830210FF"
$"FFFFFF00FED8D807070A0A0A0A0A0A0F150A0A0A0A07D8D88383838383021010"
$"FFFF00FED8FEFED8D807070A0A0A0A0F0F0A0A0A07D8D8838383838300101010"
$"FF00FED80910D8FEFED8D807070A0A0A0A0A0A07D8D8838383838300101010FF"
$"FF00FE091010099CD8FEFED8D807070A0A0A07D8D8838383838300101010FFFF"
$"00FE091010099C9C3010D8FEFED8D8070707D8D8838383838300101010FFFFFF"
$"00D80C0C099C9C3010100910D8FEFED8D8D8D8838383838300101010FFFFFFFF"
$"FF0083832C2C3010100910100910D8FED8D8838383838300101010FFFFFFFFFF"
$"FFFF333383830C0C09101009101009D8D8838383838300101010FFFFFFFFFFFF"
$"FFFFFFFF330083830C0C09101009D8D8D88383838300101010FFFFFFFFFFFFFF"
$"FFFFFFFFFFFF003383830C0C0909D8D8D883838300101010FFFFFFFFFFFFFFFF"
$"FFFFFFFFFFFFFFFF0000838309D8D8D883838300101010FFFFFFFFFFFFFFFFFF"
$"FFFFFFFFFFFFFFFFFFFF00008383D8D8838300101010FFFFFFFFFFFFFFFFFFFF"
$"FFFFFFFFFFFFFFFFFFFFFFFF280083838300101010FFFFFFFFFFFFFFFFFFFFFF"
$"FFFFFFFFFFFFFFFFFFFFFFFFFFFF000000101010FFFFFFFFFFFFFFFFFFFFFFFF"
$"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
$"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
$"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
};
resource mini_icon array
{
$"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
$"FFFFFFFFFFFFFF000000FFFFFFFFFFFF"
$"FFFFFFFFFFFF00FED8840000FFFFFFFF"
$"FFFFFFFFFF00FED8AAD8AA840000FFFF"
$"FFFFFFFF00FE0707D88484D8AAD800FF"
$"FFFFFF00FE070F090707D8AAD8D800FF"
$"FFFF00FE0709150F0F09070FD88300FF"
$"FF00FED807070909150F09D883830010"
$"00FE09FEFED807070909D88383001010"
$"000910099CFEFED807D88383001010FF"
$"0010099C9C0C09FED88383001010FFFF"
$"FF00092C0C090CD88383001010FFFFFF"
$"FFFF0000090CD88383001010FFFFFFFF"
$"FFFFFFFF00008383001010FFFFFFFFFF"
$"FFFFFFFFFFFF00001010FFFFFFFFFFFF"
$"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
};
+103
View File
@@ -0,0 +1,103 @@
/*******************************************************************************
/
/ File: SoundUtils.cpp
/
/ Description: Utility functions for handling audio data.
/
/ Copyright 1998-1999, Be Incorporated, All Rights Reserved
/
*******************************************************************************/
#include <math.h>
#include "SoundUtils.h"
// These two conversions seem to pop up all the time in media code.
// I guess it's the curse of microsecond resolution... ;-)
double
us_to_s(bigtime_t usecs)
{
return (usecs / 1000000.0);
}
bigtime_t
s_to_us(double secs)
{
return (bigtime_t) (secs * 1000000.0);
}
int
bytes_per_frame(
const media_raw_audio_format & format)
{
// The media_raw_audio_format format constants encode the
// bytes-per-sample value in the low nybble. Having a fixed
// number of bytes-per-sample, and no inter-sample relationships,
// is what makes a format "raw".
int bytesPerSample = format.format & 0xf;
return bytesPerSample * format.channel_count;
}
int
frames_per_buffer(
const media_raw_audio_format & format)
{
// This will give us the number of full-sized frames that will fit
// in a buffer. (Remember, integer division automatically rounds
// down.)
int frames = 0;
if (bytes_per_frame(format) > 0) {
frames = format.buffer_size / bytes_per_frame(format);
}
return frames;
}
bigtime_t
buffer_duration(
const media_raw_audio_format & format)
{
// Figuring out duration is easy. We take extra precaution to
// not divide by zero or return irrelevant results.
bigtime_t duration = 0;
if (format.buffer_size > 0 && format.frame_rate > 0 && bytes_per_frame(format) > 0) {
// In these kinds of calculations, it's always useful to double-check
// the unit conversions. (Anyone remember high school physics?)
// bytes/(bytes/frame) / frames/sec
// = frames * sec/frames
// = secs which is what we want.
duration = s_to_us((format.buffer_size / bytes_per_frame(format)) / format.frame_rate);
}
return duration;
}
bigtime_t
frames_duration(
const media_raw_audio_format & format, int64 num_frames)
{
// Tells us how long in us it will take to produce num_frames,
// with the given format.
bigtime_t duration = 0;
if (format.frame_rate > 0) {
duration = s_to_us(num_frames/format.frame_rate);
}
return duration;
}
int
buffers_for_duration(
const media_raw_audio_format & format, bigtime_t duration)
{
// Double-checking those unit conversions again:
// secs * ( (frames/sec) / (frames/buffer) ) = secs * (buffers/sec) = buffers
int buffers = 0;
if (frames_per_buffer(format) > 0) {
buffers = (int) ceil(us_to_s(duration)*(format.frame_rate/frames_per_buffer(format)));
}
return buffers;
}
int64
frames_for_duration(
const media_raw_audio_format & format, bigtime_t duration)
{
return (int64) ceil(format.frame_rate*us_to_s(duration));
}
+56
View File
@@ -0,0 +1,56 @@
/*******************************************************************************
/
/ File: SoundUtils.h
/
/ Description: Utility functions for handling audio data.
/
/ Copyright 1998-1999, Be Incorporated, All Rights Reserved
/
*******************************************************************************/
#if ! defined( _SoundUtils_h )
#define _SoundUtils_h
#include <MediaDefs.h>
// Simple helper functions that come in handy when doing
// buffer calculations.
double us_to_s(bigtime_t usecs);
bigtime_t s_to_us(double secs);
int bytes_per_frame(const media_raw_audio_format & format);
int frames_per_buffer(const media_raw_audio_format & format);
bigtime_t buffer_duration(const media_raw_audio_format & format);
bigtime_t frames_duration(const media_raw_audio_format & format,
int64 num_frames);
int64 frames_for_duration(const media_raw_audio_format & format,
bigtime_t duration);
int buffers_for_duration(const media_raw_audio_format & format,
bigtime_t duration);
// This is a common hook function interface for
// SoundConsumer and SoundProducer to use.
typedef void (*SoundProcessFunc)(void * cookie,
bigtime_t timestamp, void * data, size_t datasize,
const media_raw_audio_format & format);
typedef void (*SoundNotifyFunc)(void * cookie,
int32 code, ...);
// These are special codes that we use in the Notify
// function hook.
enum {
B_WILL_START = 1, // performance_time
B_WILL_STOP, // performance_time immediate
B_WILL_SEEK, // performance_time media_time
B_WILL_TIMEWARP, // real_time performance_time
B_CONNECTED, // name (char*)
B_DISCONNECTED, //
B_FORMAT_CHANGED, // media_raw_audio_format*
B_NODE_DIES, // node will die!
B_HOOKS_CHANGED, //
B_OP_TIMED_OUT, // timeout that expired -- Consumer only
B_PRODUCER_DATA_STATUS, // status performance_time -- Consumer only
B_LATE_NOTICE // how_much performance_time -- Producer only
};
#endif /* _SoundUtils_h */
+465
View File
@@ -0,0 +1,465 @@
/*
* 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 "TrackSlider.h"
#include "icon_button.h"
TrackSlider::TrackSlider(BRect rect, const char *title, BMessage *msg, uint32 resizeFlags)
: BControl(rect, "slider", NULL, msg, resizeFlags, B_WILL_DRAW | B_FRAME_EVENTS),
leftBitmap(BRect(BPoint(0,0), kLeftRightTrackSliderSize), B_CMAP8),
rightBitmap(BRect(BPoint(0,0), kLeftRightTrackSliderSize), B_CMAP8),
leftThumbBitmap(BRect(0, 0, kLeftRightThumbWidth - 1, kLeftRightThumbHeight - 1), B_CMAP8),
rightThumbBitmap(BRect(0, 0, kLeftRightThumbWidth - 1, kLeftRightThumbHeight - 1), B_CMAP8),
fLeftTime(0), fRightTime(1000000), fMainTime(0), fTotalTime(1000000),
fLeftTracking(false), fRightTracking(false), fMainTracking(false)
{
fFont.SetSize(8.0);
fFont.SetFlags(B_DISABLE_ANTIALIASING);
int32 numFamilies = count_font_families();
for (int32 i = 0; i < numFamilies; i++ ) {
font_family family;
uint32 flags;
if ((get_font_family(i, &family, &flags) == B_OK)
&& (strcmp(family, "Baskerville") == 0)) {
fFont.SetFamilyAndFace(family, B_REGULAR_FACE);
break;
}
}
leftBitmap.SetBits(kLeftTrackSliderBits, kLeftRightTrackSliderWidth * kLeftRightTrackSliderHeight, 0, B_CMAP8);
rightBitmap.SetBits(kRightTrackSliderBits, kLeftRightTrackSliderWidth * kLeftRightTrackSliderHeight, 0, B_CMAP8);
leftThumbBitmap.SetBits(kLeftThumbBits, kLeftRightThumbWidth * kLeftRightThumbHeight, 0, B_CMAP8);
rightThumbBitmap.SetBits(kRightThumbBits, kLeftRightThumbWidth * kLeftRightThumbHeight, 0, B_CMAP8);
fRight = Bounds().right - kLeftRightTrackSliderWidth;
if (fTotalTime == 0) {
fLeftX = 14;
fRightX = fRight;
fPositionX = 15;
} else {
fLeftX = 14 + (fRight - 15) * ((double)fLeftTime / fTotalTime);
fRightX = 15 + (fRight - 16) * ((double)fRightTime / fTotalTime);
fPositionX = 15 + (fRight - 14) * ((double)fMainTime / fTotalTime);
}
}
TrackSlider::~TrackSlider()
{
}
void
TrackSlider::AttachedToWindow()
{
BControl::AttachedToWindow();
SetViewColor(B_TRANSPARENT_COLOR);
}
#define SLIDER_BASE 10
void
TrackSlider::Draw(BRect updateRect)
{
SetHighColor(189,186,189);
StrokeLine(BPoint(11,SLIDER_BASE+1), BPoint(fRight,SLIDER_BASE+1));
SetHighColor(0,0,0);
StrokeLine(BPoint(11,SLIDER_BASE+2), BPoint(fRight,SLIDER_BASE+2));
SetHighColor(255,255,255);
StrokeLine(BPoint(11,SLIDER_BASE+17), BPoint(fRight,SLIDER_BASE+17));
SetHighColor(231,227,231);
StrokeLine(BPoint(11,SLIDER_BASE+18), BPoint(fRight,SLIDER_BASE+18));
SetDrawingMode(B_OP_OVER);
SetHighColor(216,216,216);
FillRect(BRect(0,1,fPositionX < 18 ? 18 : fPositionX-26,SLIDER_BASE));
FillRect(BRect(fPositionX < 18 ? 65 : fPositionX > fRight - 3 ? fRight : fPositionX+26,0,Bounds().right,SLIDER_BASE));
FillRect(BRect(0,0,Bounds().right,0));
FillRect(BRect(0,SLIDER_BASE+18,Bounds().right,SLIDER_BASE+21));
FillRect(BRect(0,0,10,SLIDER_BASE+21));
FillRect(BRect(Bounds().right - 10,0,Bounds().right,SLIDER_BASE+21));
SetLowColor(HighColor());
BPoint leftPoint(5,SLIDER_BASE+1);
DrawBitmapAsync(&leftBitmap, BRect(BPoint(0,0), kLeftRightTrackSliderSize - BPoint(5,0)),
BRect(leftPoint, leftPoint+kLeftRightTrackSliderSize-BPoint(5,0)));
BPoint rightPoint(fRight + 1,SLIDER_BASE+1);
DrawBitmapAsync(&rightBitmap, BRect(BPoint(5,0), kLeftRightTrackSliderSize),
BRect(rightPoint, rightPoint+kLeftRightTrackSliderSize-BPoint(5,0)));
SetHighColor(153,153,153);
FillRect(BRect(11,SLIDER_BASE+3,fLeftX-9,SLIDER_BASE+16));
FillRect(BRect(fRightX+9,SLIDER_BASE+3,fRight,SLIDER_BASE+16));
if (fLeftX>19) {
StrokeLine(BPoint(fLeftX-9,SLIDER_BASE+3),BPoint(fLeftX-6,SLIDER_BASE+3));
StrokeLine(BPoint(fLeftX-9,SLIDER_BASE+4),BPoint(fLeftX-7,SLIDER_BASE+4));
StrokeLine(BPoint(fLeftX-9,SLIDER_BASE+5),BPoint(fLeftX-8,SLIDER_BASE+5));
StrokeLine(BPoint(fLeftX-9,SLIDER_BASE+16),BPoint(fLeftX-6,SLIDER_BASE+16));
StrokeLine(BPoint(fLeftX-9,SLIDER_BASE+15),BPoint(fLeftX-7,SLIDER_BASE+15));
StrokeLine(BPoint(fLeftX-9,SLIDER_BASE+14),BPoint(fLeftX-8,SLIDER_BASE+14));
}
if (fRightX < fRight - 5) {
StrokeLine(BPoint(fRightX+5,SLIDER_BASE+3),BPoint(fRightX+8,SLIDER_BASE+3));
StrokeLine(BPoint(fRightX+7,SLIDER_BASE+4),BPoint(fRightX+8,SLIDER_BASE+4));
StrokeLine(BPoint(fRightX+8,SLIDER_BASE+5),BPoint(fRightX+8,SLIDER_BASE+6));
StrokeLine(BPoint(fRightX+8,SLIDER_BASE+13),BPoint(fRightX+8,SLIDER_BASE+14));
StrokeLine(BPoint(fRightX+5,SLIDER_BASE+16),BPoint(fRightX+8,SLIDER_BASE+16));
StrokeLine(BPoint(fRightX+7,SLIDER_BASE+15),BPoint(fRightX+8,SLIDER_BASE+15));
}
SetHighColor(144,186,136);
FillRect(BRect(fLeftX+1,SLIDER_BASE+3,fRightX,SLIDER_BASE+4));
FillRect(BRect(fLeftX+1,SLIDER_BASE+5,fLeftX+2,SLIDER_BASE+16));
SetHighColor(171,221,161);
FillRect(BRect(fLeftX+3,SLIDER_BASE+5,fRightX,SLIDER_BASE+16));
int i = 17;
int j = 18;
SetHighColor(128,128,128);
for (; i<fLeftX-9; i+=6) {
StrokeLine(BPoint(i,SLIDER_BASE+7), BPoint(i,SLIDER_BASE+13));
}
SetHighColor(179,179,179);
for (; j<fLeftX-9; j+=6) {
StrokeLine(BPoint(j,SLIDER_BASE+7), BPoint(j,SLIDER_BASE+13));
}
while (i<=fLeftX)
i+=6;
while (j<=fLeftX)
j+=6;
SetHighColor(144,186,136);
for (; i<=fRightX; i+=6) {
StrokeLine(BPoint(i,SLIDER_BASE+7), BPoint(i,SLIDER_BASE+13));
}
SetHighColor(189,244,178);
for (; j<=fRightX; j+=6) {
StrokeLine(BPoint(j,SLIDER_BASE+7), BPoint(j,SLIDER_BASE+13));
}
while (i<=fRightX+9)
i+=6;
while (j<=fRightX+9)
j+=6;
SetHighColor(128,128,128);
for (; i<=fRight + 1; i+=6) {
StrokeLine(BPoint(i,SLIDER_BASE+7), BPoint(i,SLIDER_BASE+13));
}
SetHighColor(179,179,179);
for (; j<=fRight + 1; j+=6) {
StrokeLine(BPoint(j,SLIDER_BASE+7), BPoint(j,SLIDER_BASE+13));
}
SetLowColor(HighColor());
BPoint leftThumbPoint(fLeftX-8,SLIDER_BASE+3);
DrawBitmapAsync(&leftThumbBitmap, BRect(BPoint(0,0), kLeftRightThumbSize - BPoint(7,0)),
BRect(leftThumbPoint, leftThumbPoint+kLeftRightThumbSize-BPoint(7,0)));
BPoint rightThumbPoint(fRightX,SLIDER_BASE+3);
DrawBitmapAsync(&rightThumbBitmap, BRect(BPoint(6,0), kLeftRightThumbSize),
BRect(rightThumbPoint, rightThumbPoint+kLeftRightThumbSize-BPoint(6,0)));
rgb_color black = {0,0,0};
rgb_color rose = {255,152,152};
rgb_color red = {255,0,0};
rgb_color bordeau = {178,0,0};
rgb_color white = {255,255,255};
DrawCounter(fMainTime, fPositionX, fMainTracking);
if (fLeftTracking)
DrawCounter(fLeftTime, fLeftX, fLeftTracking);
else if (fRightTracking)
DrawCounter(fRightTime, fRightX, fRightTracking);
BeginLineArray(30);
AddLine(BPoint(fPositionX,SLIDER_BASE+7), BPoint(fPositionX-4,SLIDER_BASE+3), black);
AddLine(BPoint(fPositionX-4,SLIDER_BASE+3), BPoint(fPositionX-4,SLIDER_BASE+1), black);
AddLine(BPoint(fPositionX-4,SLIDER_BASE+1), BPoint(fPositionX+4,SLIDER_BASE+1), black);
AddLine(BPoint(fPositionX+4,SLIDER_BASE+1), BPoint(fPositionX+4,SLIDER_BASE+3), black);
AddLine(BPoint(fPositionX+4,SLIDER_BASE+3), BPoint(fPositionX,SLIDER_BASE+7), black);
AddLine(BPoint(fPositionX-3,SLIDER_BASE+2), BPoint(fPositionX+3,SLIDER_BASE+2), rose);
AddLine(BPoint(fPositionX-3,SLIDER_BASE+3), BPoint(fPositionX-1,SLIDER_BASE+5), rose);
AddLine(BPoint(fPositionX-2,SLIDER_BASE+3), BPoint(fPositionX+2,SLIDER_BASE+3), red);
AddLine(BPoint(fPositionX-1,SLIDER_BASE+4), BPoint(fPositionX+1,SLIDER_BASE+4), red);
AddLine(BPoint(fPositionX,SLIDER_BASE+5), BPoint(fPositionX,SLIDER_BASE+5), red);
AddLine(BPoint(fPositionX,SLIDER_BASE+6), BPoint(fPositionX+3,SLIDER_BASE+3), bordeau);
AddLine(BPoint(fPositionX,SLIDER_BASE+12), BPoint(fPositionX-4,SLIDER_BASE+16), black);
AddLine(BPoint(fPositionX-4,SLIDER_BASE+16), BPoint(fPositionX-4,SLIDER_BASE+17), black);
AddLine(BPoint(fPositionX-4,SLIDER_BASE+17), BPoint(fPositionX+4,SLIDER_BASE+17), black);
AddLine(BPoint(fPositionX+4,SLIDER_BASE+17), BPoint(fPositionX+4,SLIDER_BASE+16), black);
AddLine(BPoint(fPositionX+4,SLIDER_BASE+16), BPoint(fPositionX,SLIDER_BASE+12), black);
AddLine(BPoint(fPositionX-4,SLIDER_BASE+18), BPoint(fPositionX+4,SLIDER_BASE+18), white);
AddLine(BPoint(fPositionX-3,SLIDER_BASE+16), BPoint(fPositionX,SLIDER_BASE+13), rose);
AddLine(BPoint(fPositionX-2,SLIDER_BASE+16), BPoint(fPositionX+2,SLIDER_BASE+16), red);
AddLine(BPoint(fPositionX-1,SLIDER_BASE+15), BPoint(fPositionX+1,SLIDER_BASE+15), red);
AddLine(BPoint(fPositionX,SLIDER_BASE+14), BPoint(fPositionX,SLIDER_BASE+14), red);
AddLine(BPoint(fPositionX+1,SLIDER_BASE+14), BPoint(fPositionX+3,SLIDER_BASE+16), bordeau);
EndLineArray();
Flush();
}
void
TrackSlider::DrawCounter(bigtime_t timestamp, float position, bool isTracking)
{
// timecounter
rgb_color gray = {128,128,128};
rgb_color blue = {0,0,140};
rgb_color blue2 = {146,146,214};
rgb_color white = {255,255,255};
float counterX = position;
if (counterX < 39)
counterX = 39;
if (counterX > fRight - 23)
counterX = fRight - 23;
BeginLineArray(30);
if (!isTracking) {
AddLine(BPoint(counterX-24,SLIDER_BASE+1), BPoint(counterX+24,SLIDER_BASE+1), gray);
AddLine(BPoint(counterX+25,SLIDER_BASE+1), BPoint(counterX+25,SLIDER_BASE-8), gray);
AddLine(BPoint(counterX-25,SLIDER_BASE+1), BPoint(counterX-25,SLIDER_BASE-9), white);
AddLine(BPoint(counterX-24,SLIDER_BASE-9), BPoint(counterX+25,SLIDER_BASE-9), white);
SetHighColor(216,216,216);
} else {
AddLine(BPoint(counterX-24,SLIDER_BASE+1), BPoint(counterX+24,SLIDER_BASE+1), blue);
AddLine(BPoint(counterX+25,SLIDER_BASE+1), BPoint(counterX+25,SLIDER_BASE-9), blue2);
AddLine(BPoint(counterX-25,SLIDER_BASE+1), BPoint(counterX-25,SLIDER_BASE-9), blue2);
AddLine(BPoint(counterX-24,SLIDER_BASE-9), BPoint(counterX+24,SLIDER_BASE-9), blue2);
SetHighColor(48,48,241);
}
EndLineArray();
FillRect(BRect(counterX-24,SLIDER_BASE-8,counterX+24,SLIDER_BASE));
SetDrawingMode(B_OP_COPY);
if (isTracking)
SetHighColor(255,255,255);
else
SetHighColor(0,0,0);
SetLowColor(ViewColor());
SetFont(&fFont);
char string[12];
TimeToString(timestamp, string);
DrawString(string, BPoint(counterX-22, SLIDER_BASE-1));
}
void
TrackSlider::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
{
if (!IsTracking())
return;
uint32 mouseButtons;
BPoint where;
GetMouse(&where, &mouseButtons, true);
// button not pressed, exit
if (! (mouseButtons & B_PRIMARY_MOUSE_BUTTON)) {
Invoke();
SetTracking(false);
}
UpdatePosition(point);
}
void
TrackSlider::MouseDown(BPoint point)
{
if (!Bounds().InsetBySelf(2,2).Contains(point))
return;
UpdatePosition(point);
SetTracking(true);
SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
}
void
TrackSlider::MouseUp(BPoint point)
{
if (!IsTracking())
return;
if (Bounds().InsetBySelf(2,2).Contains(point)) {
UpdatePosition(point);
}
fLeftTracking = fRightTracking = fMainTracking = false;
Invoke();
SetTracking(false);
Draw(Bounds());
Flush();
}
void
TrackSlider::UpdatePosition(BPoint point)
{
BRect leftRect(fLeftX-9, SLIDER_BASE+3, fLeftX, SLIDER_BASE+16);
BRect rightRect(fRightX, SLIDER_BASE+3, fRightX+9, SLIDER_BASE+16);
if (!(fRightTracking || fMainTracking) && (fLeftTracking || ((point.x < fPositionX-4) && leftRect.Contains(point)))) {
if (!IsTracking())
fLastX = point.x - fLeftX;
fLeftX = MIN(MAX(point.x - fLastX, 15), fRight);
fLeftTime = (bigtime_t)(MAX(MIN((fLeftX - 15) / (fRight - 14),1), 0) * fTotalTime);
fLeftTracking = true;
BMessage msg = *Message();
msg.AddInt64("left", fLeftTime);
if (fPositionX < fLeftX) {
fPositionX = fLeftX + 1;
fMainTime = fLeftTime;
msg.AddInt64("main", fMainTime);
if (fRightX < fPositionX) {
fRightX = fPositionX;
fRightTime = fMainTime;
msg.AddInt64("right", fRightTime);
}
}
Invoke(&msg);
//printf("fLeftPos : %Ld\n", fLeftTime);
} else if (!fMainTracking && (fRightTracking || ((point.x > fPositionX+4) && rightRect.Contains(point)))) {
if (!IsTracking())
fLastX = point.x - fRightX;
fRightX = MIN(MAX(point.x - fLastX, 15), fRight);
fRightTime = (bigtime_t)(MAX(MIN((fRightX - 15) / (fRight - 14),1), 0) * fTotalTime);
fRightTracking = true;
BMessage msg = *Message();
msg.AddInt64("right", fRightTime);
if (fPositionX > fRightX) {
fPositionX = fRightX;
fMainTime = fRightTime;
msg.AddInt64("main", fMainTime);
if (fLeftX > fPositionX) {
fLeftX = fPositionX - 1;
fLeftTime = fMainTime;
msg.AddInt64("left", fLeftTime);
}
}
Invoke(&msg);
//printf("fRightPos : %Ld\n", fRightTime);
} else {
fPositionX = MIN(MAX(point.x, 15), fRight);
fMainTime = (bigtime_t)(MAX(MIN((fPositionX - 15) / (fRight - 14),1), 0) * fTotalTime);
fMainTracking = true;
BMessage msg = *Message();
msg.AddInt64("main", fMainTime);
if (fRightX < fPositionX) {
fRightX = fPositionX;
fRightTime = fMainTime;
msg.AddInt64("right", fRightTime);
} else if (fLeftX > fPositionX) {
fLeftX = fPositionX - 1;
fLeftTime = fMainTime;
msg.AddInt64("left", fLeftTime);
}
Invoke(&msg);
//printf("fPosition : %Ld\n", fMainTime);
}
Draw(Bounds());
Flush();
}
void
TrackSlider::TimeToString(bigtime_t timestamp, char *string)
{
uint32 hours = timestamp / 3600000000LL;
timestamp -= hours * 3600000000LL;
uint32 minutes = timestamp / 60000000LL;
timestamp -= minutes * 60000000LL;
uint32 seconds = timestamp / 1000000LL;
timestamp -= seconds * 1000000LL;
uint32 centiseconds = timestamp / 10000LL;
sprintf(string, "%02ld:%02ld:%02ld:%02ld", hours, minutes, seconds, centiseconds);
}
void
TrackSlider::SetMainTime(bigtime_t timestamp, bool reset)
{
fMainTime = timestamp;
fPositionX = 15 + (fRight - 14) * ((double)fMainTime / fTotalTime);
if (reset) {
fRightTime = fTotalTime;
fLeftTime = 0;
fLeftX = 14 + (fRight - 15) * ((double)fLeftTime / fTotalTime);
fRightX = 15 + (fRight - 16) * ((double)fRightTime / fTotalTime);
}
Invalidate();
}
void
TrackSlider::SetTotalTime(bigtime_t timestamp, bool reset)
{
fTotalTime = timestamp;
if (reset) {
fMainTime = 0;
fRightTime = fTotalTime;
fLeftTime = 0;
}
fPositionX = 15 + (fRight - 14) * ((double)fMainTime / fTotalTime);
fLeftX = 14 + (fRight - 15) * ((double)fLeftTime / fTotalTime);
fRightX = 15 + (fRight - 16) * ((double)fRightTime / fTotalTime);
Invalidate();
}
void
TrackSlider::ResetMainTime()
{
fMainTime = fLeftTime;
fPositionX = 15 + (fRight - 14) * ((double)fMainTime / fTotalTime);
Invalidate();
}
void
TrackSlider::FrameResized(float width, float height)
{
fRight = Bounds().right - kLeftRightTrackSliderWidth;
fPositionX = 15 + (fRight - 14) * ((double)fMainTime / fTotalTime);
fLeftX = 14 + (fRight - 15) * ((double)fLeftTime / fTotalTime);
fRightX = 15 + (fRight - 16) * ((double)fRightTime / fTotalTime);
Invalidate();
}
+54
View File
@@ -0,0 +1,54 @@
/*
* 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)
*/
#ifndef TRACKSLIDER_H
#define TRACKSLIDER_H
#include <Control.h>
#include <Bitmap.h>
#include <Font.h>
class TrackSlider : public BControl
{
public:
TrackSlider(BRect rect, const char* title, BMessage *msg, uint32 resizeFlags);
~TrackSlider();
void AttachedToWindow();
virtual void Draw(BRect);
virtual void MouseMoved(BPoint point, uint32 transit, const BMessage *message);
virtual void MouseUp(BPoint point);
virtual void MouseDown(BPoint point);
void SetMainTime(bigtime_t timestamp, bool reset);
void SetTotalTime(bigtime_t timestamp, bool reset);
bigtime_t * MainTime() { return &fMainTime; };
bigtime_t RightTime() { return fRightTime; };
bigtime_t LeftTime() { return fLeftTime; };
void ResetMainTime();
virtual void FrameResized(float width, float height);
private:
void DrawCounter(bigtime_t timestamp, float position, bool isTracking);
void TimeToString(bigtime_t timestamp, char *string);
void UpdatePosition(BPoint point);
BBitmap leftBitmap, rightBitmap, leftThumbBitmap, rightThumbBitmap;
float fRight;
bigtime_t fLeftTime;
bigtime_t fRightTime;
bigtime_t fMainTime;
bigtime_t fTotalTime;
float fPositionX;
float fLeftX;
float fRightX;
float fLastX;
bool fLeftTracking;
bool fRightTracking;
bool fMainTracking;
BFont fFont;
};
#endif /* TRACKSLIDER_H */
+734
View File
@@ -0,0 +1,734 @@
/*
* 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 <Bitmap.h>
#include <Debug.h>
#include <MessageFilter.h>
#include <Window.h>
#include <map>
#include "TransportButton.h"
#include "DrawingTidbits.h"
class BitmapStash {
// Bitmap stash is a simple class to hold all the lazily-allocated
// bitmaps that the TransportButton needs when rendering itself.
// signature is a combination of the different enabled, pressed, playing, etc.
// flavors of a bitmap. If the stash does not have a particular bitmap,
// it turns around to ask the button to create one and stores it for next time.
public:
BitmapStash(TransportButton *);
~BitmapStash();
BBitmap *GetBitmap(uint32 signature);
private:
TransportButton *owner;
map<uint32, BBitmap *> stash;
};
BitmapStash::BitmapStash(TransportButton *owner)
: owner(owner)
{
}
BBitmap *
BitmapStash::GetBitmap(uint32 signature)
{
if (stash.find(signature) == stash.end()) {
BBitmap *newBits = owner->MakeBitmap(signature);
ASSERT(newBits);
stash[signature] = newBits;
}
return stash[signature];
}
BitmapStash::~BitmapStash()
{
// delete all the bitmaps
for (map<uint32, BBitmap *>::iterator i = stash.begin(); i != stash.end(); i++)
delete (*i).second;
}
class PeriodicMessageSender {
// used to send a specified message repeatedly when holding down a button
public:
static PeriodicMessageSender *Launch(BMessenger target,
const BMessage *message, bigtime_t period);
void Quit();
private:
PeriodicMessageSender(BMessenger target, const BMessage *message,
bigtime_t period);
~PeriodicMessageSender() {}
// use quit
static status_t TrackBinder(void *);
void Run();
BMessenger target;
BMessage message;
bigtime_t period;
bool requestToQuit;
};
PeriodicMessageSender::PeriodicMessageSender(BMessenger target,
const BMessage *message, bigtime_t period)
: target(target),
message(*message),
period(period),
requestToQuit(false)
{
}
PeriodicMessageSender *
PeriodicMessageSender::Launch(BMessenger target, const BMessage *message,
bigtime_t period)
{
PeriodicMessageSender *result = new PeriodicMessageSender(target, message, period);
thread_id thread = spawn_thread(&PeriodicMessageSender::TrackBinder,
"ButtonRepeatingThread", B_NORMAL_PRIORITY, result);
if (thread <= 0 || resume_thread(thread) != B_OK) {
// didn't start, don't leak self
delete result;
result = 0;
}
return result;
}
void
PeriodicMessageSender::Quit()
{
requestToQuit = true;
}
status_t
PeriodicMessageSender::TrackBinder(void *castToThis)
{
((PeriodicMessageSender *)castToThis)->Run();
return 0;
}
void
PeriodicMessageSender::Run()
{
for (;;) {
snooze(period);
if (requestToQuit)
break;
target.SendMessage(&message);
}
delete this;
}
class SkipButtonKeypressFilter : public BMessageFilter {
public:
SkipButtonKeypressFilter(uint32 shortcutKey, uint32 shortcutModifier,
TransportButton *target);
protected:
filter_result Filter(BMessage *message, BHandler **handler);
private:
uint32 shortcutKey;
uint32 shortcutModifier;
TransportButton *target;
};
SkipButtonKeypressFilter::SkipButtonKeypressFilter(uint32 shortcutKey,
uint32 shortcutModifier, TransportButton *target)
: BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE),
shortcutKey(shortcutKey),
shortcutModifier(shortcutModifier),
target(target)
{
}
filter_result
SkipButtonKeypressFilter::Filter(BMessage *message, BHandler **handler)
{
if (target->IsEnabled()
&& (message->what == B_KEY_DOWN || message->what == B_KEY_UP)) {
uint32 modifiers;
uint32 rawKeyChar = 0;
uint8 byte = 0;
int32 key = 0;
if (message->FindInt32("modifiers", (int32 *)&modifiers) != B_OK
|| message->FindInt32("raw_char", (int32 *)&rawKeyChar) != B_OK
|| message->FindInt8("byte", (int8 *)&byte) != B_OK
|| message->FindInt32("key", &key) != B_OK)
return B_DISPATCH_MESSAGE;
modifiers &= B_SHIFT_KEY | B_COMMAND_KEY | B_CONTROL_KEY
| B_OPTION_KEY | B_MENU_KEY;
// strip caps lock, etc.
if (modifiers == shortcutModifier && rawKeyChar == shortcutKey) {
if (message->what == B_KEY_DOWN)
target->ShortcutKeyDown();
else
target->ShortcutKeyUp();
return B_SKIP_MESSAGE;
}
}
// let others deal with this
return B_DISPATCH_MESSAGE;
}
TransportButton::TransportButton(BRect frame, const char *name,
const unsigned char *normalBits,
const unsigned char *pressedBits,
const unsigned char *disabledBits,
BMessage *invokeMessage, BMessage *startPressingMessage,
BMessage *pressingMessage, BMessage *donePressingMessage, bigtime_t period,
uint32 key, uint32 modifiers, uint32 resizeFlags)
: BControl(frame, name, "", invokeMessage, resizeFlags, B_WILL_DRAW | B_NAVIGABLE),
bitmaps(new BitmapStash(this)),
normalBits(normalBits),
pressedBits(pressedBits),
disabledBits(disabledBits),
startPressingMessage(startPressingMessage),
pressingMessage(pressingMessage),
donePressingMessage(donePressingMessage),
pressingPeriod(period),
mouseDown(false),
keyDown(false),
messageSender(0),
keyPressFilter(0)
{
if (key)
keyPressFilter = new SkipButtonKeypressFilter(key, modifiers, this);
}
void
TransportButton::AttachedToWindow()
{
_inherited::AttachedToWindow();
if (keyPressFilter)
Window()->AddCommonFilter(keyPressFilter);
// transparent to reduce flicker
SetViewColor(B_TRANSPARENT_COLOR);
}
void
TransportButton::DetachedFromWindow()
{
if (keyPressFilter) {
Window()->RemoveCommonFilter(keyPressFilter);
delete keyPressFilter;
}
_inherited::DetachedFromWindow();
}
TransportButton::~TransportButton()
{
delete startPressingMessage;
delete pressingMessage;
delete donePressingMessage;
delete bitmaps;
}
void
TransportButton::WindowActivated(bool state)
{
if (!state)
ShortcutKeyUp();
_inherited::WindowActivated(state);
}
void
TransportButton::SetEnabled(bool on)
{
_inherited::SetEnabled(on);
if (!on)
ShortcutKeyUp();
}
const unsigned char *
TransportButton::BitsForMask(uint32 mask) const
{
switch (mask) {
case 0:
return normalBits;
case kDisabledMask:
return disabledBits;
case kPressedMask:
return pressedBits;
default:
break;
}
TRESPASS();
return 0;
}
BBitmap *
TransportButton::MakeBitmap(uint32 mask)
{
BBitmap *result = new BBitmap(Bounds(), B_COLOR_8_BIT);
result->SetBits(BitsForMask(mask), (Bounds().Width() + 1) * (Bounds().Height() + 1),
0, B_COLOR_8_BIT);
ReplaceTransparentColor(result, Parent()->ViewColor());
return result;
}
uint32
TransportButton::ModeMask() const
{
return (IsEnabled() ? 0 : kDisabledMask)
| (Value() ? kPressedMask : 0);
}
void
TransportButton::Draw(BRect)
{
DrawBitmapAsync(bitmaps->GetBitmap(ModeMask()));
}
void
TransportButton::StartPressing()
{
SetValue(1);
if (startPressingMessage)
Invoke(startPressingMessage);
if (pressingMessage) {
ASSERT(pressingMessage);
messageSender = PeriodicMessageSender::Launch(Messenger(),
pressingMessage, pressingPeriod);
}
}
void
TransportButton::MouseCancelPressing()
{
if (!mouseDown || keyDown)
return;
mouseDown = false;
if (pressingMessage) {
ASSERT(messageSender);
PeriodicMessageSender *sender = messageSender;
messageSender = 0;
sender->Quit();
}
if (donePressingMessage)
Invoke(donePressingMessage);
SetValue(0);
}
void
TransportButton::DonePressing()
{
if (pressingMessage) {
ASSERT(messageSender);
PeriodicMessageSender *sender = messageSender;
messageSender = 0;
sender->Quit();
}
Invoke();
SetValue(0);
}
void
TransportButton::MouseStartPressing()
{
if (mouseDown)
return;
mouseDown = true;
if (!keyDown)
StartPressing();
}
void
TransportButton::MouseDonePressing()
{
if (!mouseDown)
return;
mouseDown = false;
if (!keyDown)
DonePressing();
}
void
TransportButton::ShortcutKeyDown()
{
if (!IsEnabled())
return;
if (keyDown)
return;
keyDown = true;
if (!mouseDown)
StartPressing();
}
void
TransportButton::ShortcutKeyUp()
{
if (!keyDown)
return;
keyDown = false;
if (!mouseDown)
DonePressing();
}
void
TransportButton::MouseDown(BPoint)
{
if (!IsEnabled())
return;
ASSERT(Window()->Flags() & B_ASYNCHRONOUS_CONTROLS);
SetTracking(true);
SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
MouseStartPressing();
}
void
TransportButton::MouseMoved(BPoint point, uint32 code, const BMessage *)
{
if (IsTracking() && Bounds().Contains(point) != Value()) {
if (!Value())
MouseStartPressing();
else
MouseCancelPressing();
}
}
void
TransportButton::MouseUp(BPoint point)
{
if (IsTracking()) {
if (Bounds().Contains(point))
MouseDonePressing();
else
MouseCancelPressing();
SetTracking(false);
}
}
void
TransportButton::SetStartPressingMessage(BMessage *message)
{
delete startPressingMessage;
startPressingMessage = message;
}
void
TransportButton::SetPressingMessage(BMessage *message)
{
delete pressingMessage;
pressingMessage = message;
}
void
TransportButton::SetDonePressingMessage(BMessage *message)
{
delete donePressingMessage;
donePressingMessage = message;
}
void
TransportButton::SetPressingPeriod(bigtime_t newTime)
{
pressingPeriod = newTime;
}
PlayPauseButton::PlayPauseButton(BRect frame, const char *name,
BMessage *invokeMessage, BMessage *blinkMessage,
uint32 key, uint32 modifiers, uint32 resizeFlags)
: TransportButton(frame, name, kPlayButtonBitmapBits, kPressedPlayButtonBitmapBits,
kDisabledPlayButtonBitmapBits, invokeMessage, NULL,
NULL, NULL, 0, key, modifiers, resizeFlags),
fState(PlayPauseButton::kStopped),
fLastModeMask(0),
fRunner(NULL),
fBlinkMessage(blinkMessage)
{
}
void
PlayPauseButton::SetStopped()
{
if (fState == kStopped || fState == kAboutToPlay)
return;
fState = kStopped;
delete fRunner;
fRunner = NULL;
Invalidate();
}
void
PlayPauseButton::SetPlaying()
{
if (fState == kAboutToPause)
return;
// in playing state blink the LED on and off
if (fState == kPlayingLedOn)
fState = kPlayingLedOff;
else
fState = kPlayingLedOn;
Invalidate();
}
const bigtime_t kPlayingBlinkPeriod = 600000;
void
PlayPauseButton::SetPaused()
{
if (fState == kAboutToPlay)
return;
// in paused state blink the LED on and off
if (fState == kPausedLedOn)
fState = kPausedLedOff;
else
fState = kPausedLedOn;
Invalidate();
}
uint32
PlayPauseButton::ModeMask() const
{
if (!IsEnabled())
return kDisabledMask;
uint32 result = 0;
if (Value())
result = kPressedMask;
if (fState == kPlayingLedOn || fState == kAboutToPlay)
result |= kPlayingMask;
else if (fState == kAboutToPause || fState == kPausedLedOn)
result |= kPausedMask;
return result;
}
const unsigned char *
PlayPauseButton::BitsForMask(uint32 mask) const
{
switch (mask) {
case kPlayingMask:
return kPlayingPlayButtonBitmapBits;
case kPlayingMask | kPressedMask:
return kPressedPlayingPlayButtonBitmapBits;
case kPausedMask:
return kPausedPlayButtonBitmapBits;
case kPausedMask | kPressedMask:
return kPressedPausedPlayButtonBitmapBits;
default:
return _inherited::BitsForMask(mask);
}
TRESPASS();
return 0;
}
void
PlayPauseButton::StartPressing()
{
if (fState == kPlayingLedOn || fState == kPlayingLedOff)
fState = kAboutToPause;
else
fState = kAboutToPlay;
_inherited::StartPressing();
}
void
PlayPauseButton::MouseCancelPressing()
{
if (fState == kAboutToPause)
fState = kPlayingLedOn;
else
fState = kStopped;
_inherited::MouseCancelPressing();
}
void
PlayPauseButton::DonePressing()
{
if (fState == kAboutToPause) {
fState = kPausedLedOn;
} else if (fState == kAboutToPlay) {
fState = kPlayingLedOn;
if (!fRunner && fBlinkMessage)
fRunner = new BMessageRunner(Messenger(), fBlinkMessage, kPlayingBlinkPeriod);
}
_inherited::DonePressing();
}
RecordButton::RecordButton(BRect frame, const char *name,
BMessage *invokeMessage, BMessage *blinkMessage,
uint32 key, uint32 modifiers, uint32 resizeFlags)
: TransportButton(frame, name, kRecordButtonBitmapBits, kPressedRecordButtonBitmapBits,
kDisabledRecordButtonBitmapBits, invokeMessage, NULL, NULL,
NULL, 0, key, modifiers, resizeFlags),
fState(RecordButton::kStopped),
fLastModeMask(0),
fRunner(NULL),
fBlinkMessage(blinkMessage)
{
}
void
RecordButton::SetStopped()
{
if (fState == kStopped || fState == kAboutToRecord)
return;
fState = kStopped;
delete fRunner;
fRunner = NULL;
Invalidate();
}
const bigtime_t kRecordingBlinkPeriod = 600000;
void
RecordButton::SetRecording()
{
if (fState == kAboutToStop)
return;
if (fState == kRecordingLedOff)
fState = kRecordingLedOn;
else
fState = kRecordingLedOff;
Invalidate();
}
uint32
RecordButton::ModeMask() const
{
if (!IsEnabled())
return kDisabledMask;
uint32 result = 0;
if (Value())
result = kPressedMask;
if (fState == kAboutToStop || fState == kRecordingLedOn)
result |= kRecordingMask;
return result;
}
const unsigned char *
RecordButton::BitsForMask(uint32 mask) const
{
switch (mask) {
case kRecordingMask:
return kRecordingRecordButtonBitmapBits;
case kRecordingMask | kPressedMask:
return kPressedRecordingRecordButtonBitmapBits;
default:
return _inherited::BitsForMask(mask);
}
TRESPASS();
return 0;
}
void
RecordButton::StartPressing()
{
if (fState == kRecordingLedOn || fState == kRecordingLedOff)
fState = kAboutToStop;
else
fState = kAboutToRecord;
_inherited::StartPressing();
}
void
RecordButton::MouseCancelPressing()
{
if (fState == kAboutToStop)
fState = kRecordingLedOn;
else
fState = kStopped;
_inherited::MouseCancelPressing();
}
void
RecordButton::DonePressing()
{
if (fState == kAboutToStop) {
fState = kStopped;
delete fRunner;
fRunner = NULL;
} else if (fState == kAboutToRecord) {
fState = kRecordingLedOn;
if (!fRunner && fBlinkMessage)
fRunner = new BMessageRunner(Messenger(), fBlinkMessage, kRecordingBlinkPeriod);
}
_inherited::DonePressing();
}
+212
View File
@@ -0,0 +1,212 @@
/*
* 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)
*/
#ifndef __MEDIA_BUTTON__
#define __MEDIA_BUTTON__
#include <Control.h>
#include <MessageRunner.h>
#include "icon_button.h"
class BMessage;
class BBitmap;
class PeriodicMessageSender;
class BitmapStash;
// TransportButton must be installed into a window with B_ASYNCHRONOUS_CONTROLS on
// currently no button focus drawing
class TransportButton : public BControl {
public:
TransportButton(BRect frame, const char *name,
const unsigned char *normalBits,
const unsigned char *pressedBits,
const unsigned char *disabledBits,
BMessage *invokeMessage, // done pressing over button
BMessage *startPressingMessage = 0, // just clicked button
BMessage *pressingMessage = 0, // periodical still pressing
BMessage *donePressing = 0, // tracked out of button/didn't invoke
bigtime_t period = 0, // pressing message period
uint32 key = 0, // optional shortcut key
uint32 modifiers = 0, // optional shortcut key modifier
uint32 resizeFlags = B_FOLLOW_LEFT | B_FOLLOW_TOP);
virtual ~TransportButton();
void SetStartPressingMessage(BMessage *);
void SetPressingMessage(BMessage *);
void SetDonePressingMessage(BMessage *);
void SetPressingPeriod(bigtime_t);
virtual void SetEnabled(bool);
protected:
enum {
kDisabledMask = 0x1,
kPressedMask = 0x2
};
virtual void AttachedToWindow();
virtual void DetachedFromWindow();
virtual void Draw(BRect);
virtual void MouseDown(BPoint);
virtual void MouseMoved(BPoint, uint32 code, const BMessage *);
virtual void MouseUp(BPoint);
virtual void WindowActivated(bool);
virtual BBitmap *MakeBitmap(uint32);
// lazy bitmap builder
virtual uint32 ModeMask() const;
// mode mask corresponding to the current button state
// - determines which bitmap will be used
virtual const unsigned char *BitsForMask(uint32) const;
// pick the right bits based on a mode mask
// overriding class can add swapping between two pairs of bitmaps, etc.
virtual void StartPressing();
virtual void MouseCancelPressing();
virtual void DonePressing();
private:
void ShortcutKeyDown();
void ShortcutKeyUp();
void MouseStartPressing();
void MouseDonePressing();
BitmapStash *bitmaps;
// using BitmapStash * here instead of a direct member so that the class can be private in
// the .cpp file
// bitmap bits used to build bitmaps for the different states
const unsigned char *normalBits;
const unsigned char *pressedBits;
const unsigned char *disabledBits;
BMessage *startPressingMessage;
BMessage *pressingMessage;
BMessage *donePressingMessage;
bigtime_t pressingPeriod;
bool mouseDown;
bool keyDown;
PeriodicMessageSender *messageSender;
BMessageFilter *keyPressFilter;
typedef BControl _inherited;
friend class SkipButtonKeypressFilter;
friend class BitmapStash;
};
class PlayPauseButton : public TransportButton {
// Knows about playing and paused states, blinks
// the pause LED during paused state
public:
PlayPauseButton(BRect frame, const char *name,
BMessage *invokeMessage, // done pressing over button
BMessage *blinkMessage = 0, // blinking
uint32 key = 0, // optional shortcut key
uint32 modifiers = 0, // optional shortcut key modifier
uint32 resizeFlags = B_FOLLOW_LEFT | B_FOLLOW_TOP);
// These need get called periodically to update the button state
// OK to call them over and over - once the state is correct, the call
// is very low overhead
void SetStopped();
void SetPlaying();
void SetPaused();
protected:
virtual uint32 ModeMask() const;
virtual const unsigned char *BitsForMask(uint32) const;
virtual void StartPressing();
virtual void MouseCancelPressing();
virtual void DonePressing();
private:
enum PlayState {
kStopped,
kAboutToPlay,
kPlayingLedOn,
kPlayingLedOff,
kAboutToPause,
kPausedLedOn,
kPausedLedOff
};
enum {
kPlayingMask = 0x4,
kPausedMask = 0x8
};
PlayState fState;
uint32 fLastModeMask;
BMessageRunner *fRunner;
BMessage *fBlinkMessage;
typedef TransportButton _inherited;
};
class RecordButton : public TransportButton {
// Knows about recording states, blinks
// the recording LED during recording state
public:
RecordButton(BRect frame, const char *name,
BMessage *invokeMessage, // done pressing over button
BMessage *blinkMessage = 0, // blinking
uint32 key = 0, // optional shortcut key
uint32 modifiers = 0, // optional shortcut key modifier
uint32 resizeFlags = B_FOLLOW_LEFT | B_FOLLOW_TOP);
// These need get called periodically to update the button state
// OK to call them over and over - once the state is correct, the call
// is very low overhead
void SetStopped();
void SetRecording();
protected:
virtual uint32 ModeMask() const;
virtual const unsigned char *BitsForMask(uint32) const;
virtual void StartPressing();
virtual void MouseCancelPressing();
virtual void DonePressing();
private:
enum RecordState {
kAboutToStop,
kStopped,
kAboutToRecord,
kRecordingLedOn,
kRecordingLedOff
};
enum {
kRecordingMask = 0x4
};
enum {
RECORD_PRESSING = 'crpr'
};
RecordState fState;
uint32 fLastModeMask;
BMessageRunner *fRunner;
BMessage *fBlinkMessage;
typedef TransportButton _inherited;
};
#endif
+100
View File
@@ -0,0 +1,100 @@
/*
* 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 "UpDownButton.h"
#include "icon_button.h"
UpDownButton::UpDownButton(BRect rect, BMessage *msg, uint32 resizeFlags)
: BControl(rect, "button", NULL, msg, resizeFlags, B_WILL_DRAW),
fLastValue(B_CONTROL_ON)
{
BRect rect = BRect(0, 0, kUpDownButtonWidth - 1, kUpDownButtonHeight - 1);
fBitmapUp = new BBitmap(rect, B_CMAP8);
fBitmapUp->SetBits(kButtonUpBits, kUpDownButtonWidth * kUpDownButtonHeight, 0, B_CMAP8);
fBitmapDown = new BBitmap(rect, B_CMAP8);
fBitmapDown->SetBits(kButtonDownBits, kUpDownButtonWidth * kUpDownButtonHeight, 0, B_CMAP8);
fBitmapMiddle = new BBitmap(rect, B_CMAP8);
fBitmapMiddle->SetBits(kButtonMiddleBits, kUpDownButtonWidth * kUpDownButtonHeight, 0, B_CMAP8);
}
UpDownButton::~UpDownButton()
{
delete fBitmapUp;
delete fBitmapDown;
delete fBitmapMiddle;
}
void
UpDownButton::Draw(BRect updateRect)
{
SetDrawingMode(B_OP_OVER);
if(IsTracking()) {
if((Bounds().top + Bounds().Height()/2) > (fTrackingY + 3))
DrawBitmap(fBitmapUp);
else if((Bounds().top + Bounds().Height()/2) < (fTrackingY - 3))
DrawBitmap(fBitmapDown);
else
DrawBitmap(fBitmapMiddle);
} else {
if(Value()==B_CONTROL_OFF)
DrawBitmap(fBitmapUp);
else
DrawBitmap(fBitmapDown);
}
SetDrawingMode(B_OP_COPY);
}
void
UpDownButton::MouseDown(BPoint point)
{
if(!IsEnabled())
return;
fLastValue = Value();
fTrackingY = (Bounds().top + Bounds().Height()/2);
SetTracking(true);
SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
SetValue(Value() == B_CONTROL_ON ? B_CONTROL_OFF : B_CONTROL_ON);
}
void
UpDownButton::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
{
if (!IsTracking())
return;
fTrackingY = point.y;
Draw(Bounds());
Flush();
}
void
UpDownButton::MouseUp(BPoint point)
{
if (!IsTracking())
return;
if((Bounds().top + Bounds().Height()/2) > (fTrackingY + 3))
SetValue(B_CONTROL_ON);
else if((Bounds().top + Bounds().Height()/2) < (fTrackingY - 3))
SetValue(B_CONTROL_OFF);
if(Value()!=fLastValue)
Invoke();
SetTracking(false);
Draw(Bounds());
Flush();
fLastValue = Value();
}
+31
View File
@@ -0,0 +1,31 @@
/*
* 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)
*/
#ifndef UPDOWNBUTTON_H
#define UPDOWNBUTTON_H
#include <Bitmap.h>
#include <Control.h>
#define DRAG_ITEM 'dndi'
class UpDownButton : public BControl
{
public:
UpDownButton(BRect rect, BMessage *msg, uint32 resizeFlags = 0);
~UpDownButton();
virtual void Draw(BRect);
virtual void MouseDown(BPoint point);
virtual void MouseMoved(BPoint point, uint32 transit, const BMessage *message);
virtual void MouseUp(BPoint point);
private:
BBitmap *fBitmapUp, *fBitmapDown, *fBitmapMiddle;
float fTrackingY;
int32 fLastValue;
};
#endif
+194
View File
@@ -0,0 +1,194 @@
/*
* 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 <Screen.h>
#include <Window.h>
#include "DrawingTidbits.h"
#include "VUView.h"
const rgb_color back_color = {12, 36, 12};
const rgb_color low_color = {40, 120, 40};
const rgb_color high_color = {240, 255, 240};
VUView::VUView(BRect rect, uint32 resizeFlags)
: BView(rect, "vumeter", resizeFlags, B_WILL_DRAW),
fThreadId(-1),
fBitmap(NULL),
fQuitting(false)
{
fLevelCount = int(rect.Height()) / 2;
fChannels = 2;
fCurrentLevels = new int32[fChannels];
for (int channel=0; channel < fChannels; channel++)
fCurrentLevels[channel] = 0;
fBitmap = new BBitmap(rect, BScreen().ColorSpace(), true);
memset(fBitmap->Bits(), 0, fBitmap->BitsLength());
fBitmapView = new BView(rect, "bitmapView", B_FOLLOW_LEFT|B_FOLLOW_TOP, B_WILL_DRAW);
fBitmap->AddChild(fBitmapView);
}
VUView::~VUView()
{
}
void
VUView::AttachedToWindow()
{
SetViewColor(B_TRANSPARENT_COLOR);
Run();
}
void
VUView::DetachedFromWindow()
{
Quit();
}
void
VUView::Draw(BRect updateRect)
{
DrawBitmap(fBitmap);
Sync();
}
void
VUView::Run()
{
fThreadId = spawn_thread(&RenderLaunch, "VU view", B_NORMAL_PRIORITY, this);
if (fThreadId < 0)
return;
resume_thread(fThreadId);
}
void
VUView::Quit()
{
fQuitting = true;
snooze(10000);
kill_thread(fThreadId);
}
int32
VUView::RenderLaunch(void *data)
{
VUView *vu = (VUView*) data;
vu->RenderLoop();
return B_OK;
}
#define SHIFT_UNTIL(value,shift,min) value = (value - shift > min) ? (value - shift) : min
void
VUView::RenderLoop()
{
rgb_color levels[fLevelCount][2];
for (int32 i=0; i<fLevelCount; i++) {
levels[i][0] = levels[i][1] = back_color;
}
int32 level = 0;
while (!fQuitting) {
/* computing */
for (int32 channel = 0; channel < 2; channel++) {
level = fCurrentLevels[channel];
for (int32 i=0; i<level; i++) {
if (levels[i][channel].red >= 90) {
SHIFT_UNTIL(levels[i][channel].red, 15, low_color.red);
SHIFT_UNTIL(levels[i][channel].blue, 15, low_color.blue);
} else {
SHIFT_UNTIL(levels[i][channel].red, 7, low_color.red);
SHIFT_UNTIL(levels[i][channel].blue, 7, low_color.blue);
SHIFT_UNTIL(levels[i][channel].green, 14, low_color.green);
}
}
levels[level][channel] = high_color;
for (int32 i=level+1; i<fLevelCount; i++) {
if (levels[i][channel].red >= 85) {
SHIFT_UNTIL(levels[i][channel].red, 15, back_color.red);
SHIFT_UNTIL(levels[i][channel].blue, 15, back_color.blue);
} else {
SHIFT_UNTIL(levels[i][channel].red, 7, back_color.red);
SHIFT_UNTIL(levels[i][channel].blue, 7, back_color.blue);
SHIFT_UNTIL(levels[i][channel].green, 14, back_color.green);
}
}
}
/* rendering */
fBitmap->Lock();
fBitmapView->BeginLineArray(fLevelCount * 2);
BPoint start1, end1, start2, end2;
start1.x = 1;
start2.x = 20;
end1.x = 14;
end2.x = 33;
start1.y = end1.y = start2.y = end2.y = 2;
for (int32 i=fLevelCount-1; i>=0; i--) {
fBitmapView->AddLine(start1, end1, levels[i][0]);
fBitmapView->AddLine(start2, end2, levels[i][1]);
start1.y = end1.y = start2.y = end2.y = end2.y + 2;
}
fBitmapView->EndLineArray();
fBitmap->Unlock();
/* ask drawing */
if (Window()->LockWithTimeout(5000) == B_OK) {
Invalidate();
Window()->Unlock();
snooze(50000);
}
}
}
void
VUView::ComputeNextLevel(void *data, size_t size)
{
int16* samp = (int16*)data;
for (int32 channel = 0; channel < fChannels; channel++) {
// get the min and max values in the nibbling interval
// and set max to be the greater of the absolute value
// of these.
int mi = 0, ma = 0;
for (uint32 ix=channel; ix<size/sizeof(uint16); ix += fChannels) {
if (mi > samp[ix]) mi = samp[ix];
else if (ma < samp[ix]) ma = samp[ix];
}
if (-ma > mi) ma = (mi == -32768) ? 32767 : -mi;
uint8 n = ma / (2 << (16-7));
fCurrentLevels[channel] = n;
if (fCurrentLevels[channel] < 0)
fCurrentLevels[channel] = 0;
}
}
+40
View File
@@ -0,0 +1,40 @@
/*
* 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)
*/
#ifndef VUVIEW_H
#define VUVIEW_H
#include <Bitmap.h>
#include <View.h>
class VUView : public BView
{
public:
VUView(BRect rect, uint32 resizeFlags);
~VUView();
void AttachedToWindow();
void DetachedFromWindow();
void Draw(BRect updateRect);
void ComputeNextLevel(void *data, size_t size);
private:
void Run();
void Quit();
static int32 RenderLaunch(void *data);
void RenderLoop();
thread_id fThreadId;
BBitmap *fBitmap;
BView *fBitmapView;
bool fQuitting;
int32 fLevelCount;
int32 *fCurrentLevels;
int32 fChannels;
};
#endif /* VUVIEW_H */
+176
View File
@@ -0,0 +1,176 @@
/*
* 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 "VolumeSlider.h"
#include "icon_button.h"
#define VOLUME_CHANGED 'vlcg'
#define RATIO 2.0f
VolumeSlider::VolumeSlider(BRect rect, const char *title, uint32 resizeFlags)
: BControl(rect, "slider", NULL, new BMessage(VOLUME_CHANGED), resizeFlags, B_WILL_DRAW),
fLeftBitmap(BRect(0, 0, kLeftVolumeWidth - 1, kLeftVolumeHeight - 1), B_CMAP8),
fRightBitmap(BRect(0, 0, kRightVolumeWidth - 1, kRightVolumeHeight - 1), B_CMAP8),
fButtonBitmap(BRect(0, 0, kThumbWidth - 1, kThumbHeight - 1), B_CMAP8),
fSoundPlayer(NULL)
{
fLeftBitmap.SetBits(kLeftVolumeBits, kLeftVolumeWidth * kLeftVolumeHeight, 0, B_CMAP8);
fRightBitmap.SetBits(kRightVolumeBits, kRightVolumeWidth * kRightVolumeHeight, 0, B_CMAP8);
fButtonBitmap.SetBits(kThumbBits, kThumbWidth * kThumbHeight, 0, B_CMAP8);
fRight = Bounds().right - 15;
}
VolumeSlider::~VolumeSlider()
{
}
void
VolumeSlider::Draw(BRect updateRect)
{
SetHighColor(189,186,189);
StrokeLine(BPoint(11,1), BPoint(fRight,1));
SetHighColor(0,0,0);
StrokeLine(BPoint(11,2), BPoint(fRight,2));
SetHighColor(255,255,255);
StrokeLine(BPoint(11,14), BPoint(fRight,14));
SetHighColor(231,227,231);
StrokeLine(BPoint(11,15), BPoint(fRight,15));
SetLowColor(ViewColor());
SetDrawingMode(B_OP_OVER);
DrawBitmapAsync(&fLeftBitmap, BPoint(5,1));
DrawBitmapAsync(&fRightBitmap, BPoint(fRight + 1,1));
float position = 11 + (fRight - 11) * (fSoundPlayer ? fSoundPlayer->Volume() / RATIO : 0);
SetHighColor(102,152,102);
FillRect(BRect(11,3,position,4));
SetHighColor(152,203,152);
FillRect(BRect(11,5,position,13));
if (fSoundPlayer)
SetHighColor(152,152,152);
else
SetHighColor(200,200,200);
FillRect(BRect(position,3,fRight,13));
SetHighColor(102,152,102);
for (int i=15; i<=fRight + 1; i+=5) {
if (i>position)
SetHighColor(128,128,128);
StrokeLine(BPoint(i,8), BPoint(i,9));
}
DrawBitmapAsync(&fButtonBitmap, BPoint(position-5,3));
Sync();
SetDrawingMode(B_OP_COPY);
}
void
VolumeSlider::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
{
if (!IsTracking())
return;
uint32 mouseButtons;
BPoint where;
GetMouse(&where, &mouseButtons, true);
// button not pressed, exit
if (! (mouseButtons & B_PRIMARY_MOUSE_BUTTON)) {
Invoke();
SetTracking(false);
}
if (!fSoundPlayer || !Bounds().InsetBySelf(2,2).Contains(point))
return;
UpdateVolume(point);
}
void
VolumeSlider::MouseDown(BPoint point)
{
if (!fSoundPlayer || !Bounds().InsetBySelf(2,2).Contains(point))
return;
UpdateVolume(point);
SetTracking(true);
SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
}
void
VolumeSlider::MouseUp(BPoint point)
{
if (!IsTracking())
return;
if (fSoundPlayer && Bounds().InsetBySelf(2,2).Contains(point)) {
UpdateVolume(point);
}
Invoke();
SetTracking(false);
Draw(Bounds());
Flush();
}
void
VolumeSlider::UpdateVolume(BPoint point)
{
fVolume = MIN(MAX(point.x, 11), fRight);
fVolume = (fVolume - 11) / (fRight - 11);
fVolume = MAX(MIN(fVolume,1), 0);
Draw(Bounds());
Flush();
if (fSoundPlayer)
fSoundPlayer->SetVolume(fVolume * RATIO);
}
void
VolumeSlider::SetSoundPlayer(BSoundPlayer *player)
{
fSoundPlayer = player;
Invalidate();
}
SpeakerView::SpeakerView(BRect rect, uint32 resizeFlags)
: BBox(rect, "speaker", resizeFlags, B_WILL_DRAW, B_NO_BORDER),
fSpeakerBitmap(BRect(0, 0, kSpeakerIconBitmapWidth - 1, kSpeakerIconBitmapHeight - 1), B_CMAP8)
{
fSpeakerBitmap.SetBits(kSpeakerIconBits, kSpeakerIconBitmapWidth * kSpeakerIconBitmapHeight, 0, B_CMAP8);
}
SpeakerView::~SpeakerView()
{
}
void
SpeakerView::Draw(BRect updateRect)
{
SetDrawingMode(B_OP_OVER);
DrawBitmap(&fSpeakerBitmap);
SetDrawingMode(B_OP_COPY);
}
+44
View File
@@ -0,0 +1,44 @@
/*
* 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)
*/
#ifndef VOLUMESLIDER_H
#define VOLUMESLIDER_H
#include <Control.h>
#include <Bitmap.h>
#include <Box.h>
#include <SoundPlayer.h>
class VolumeSlider : public BControl
{
public:
VolumeSlider(BRect rect, const char* title, uint32 resizeFlags);
~VolumeSlider();
virtual void Draw(BRect);
virtual void MouseMoved(BPoint point, uint32 transit, const BMessage *message);
virtual void MouseUp(BPoint point);
virtual void MouseDown(BPoint point);
void SetSoundPlayer(BSoundPlayer *player);
private:
void UpdateVolume(BPoint point);
BBitmap fLeftBitmap, fRightBitmap, fButtonBitmap;
float fRight;
float fVolume;
BSoundPlayer *fSoundPlayer;
};
class SpeakerView : public BBox
{
public:
SpeakerView(BRect rect, uint32 resizeFlags);
~SpeakerView();
void Draw(BRect updateRect);
private:
BBitmap fSpeakerBitmap;
};
#endif
+30
View File
@@ -0,0 +1,30 @@
/*******************************************************************************
/
/ File: array_delete.h
/
/ Description: Template for deleting a new[] array of something.
/
/ Copyright 1998-1999, Be Incorporated, All Rights Reserved
/
*******************************************************************************/
#if !defined( _array_delete_h )
#define _array_delete_h
// Oooh! It's a template!
template<class C> class array_delete {
C * & m_ptr;
public:
// auto_ptr<> uses delete, not delete[], so we have to write our own.
// I like hanging on to a reference, because if we manually delete the
// array and set the pointer to NULL (or otherwise change the pointer)
// it will still work. Others like the more elaborate implementation
// of auto_ptr<>. Your Mileage May Vary.
array_delete(C * & ptr) : m_ptr(ptr) {}
~array_delete() { delete[] m_ptr; }
};
#endif /* array_delete_h */
File diff suppressed because it is too large Load Diff