root/src/apps/soundrecorder/RecorderWindow.cpp
/*
 * Copyright 2014, Dario Casalinuovo. All rights reserved.
 * 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 <Application.h>
#include <Alert.h>
#include <Debug.h>
#include <Screen.h>
#include <Button.h>
#include <CheckBox.h>
#include <TextControl.h>
#include <MenuField.h>
#include <PopUpMenu.h>
#include <MenuItem.h>
#include <Box.h>
#include <ScrollView.h>
#include <Beep.h>
#include <StringView.h>
#include <String.h>
#include <Slider.h>
#include <Message.h>

#include <Path.h>
#include <FindDirectory.h>
#include <MediaAddOn.h>

#include <SoundPlayer.h>

#include <assert.h>
#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>

#include <MediaRoster.h>
#include <TimeSource.h>
#include <NodeInfo.h>

#include "RecorderWindow.h"
#include "FileUtils.h"

#if ! NDEBUG
#define FPRINTF(args) fprintf args
#else
#define FPRINTF(args)
#endif

#define DEATH FPRINTF
#define CONNECT FPRINTF
#define WINDOW FPRINTF

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "RecorderWindow"


// default window positioning
static const float MIN_WIDTH = 400.0f;
static const float MIN_HEIGHT = 175.0f;
static const float XPOS = 100.0f;
static const float YPOS = 200.0f;

#define FOURCC(a,b,c,d) ((((uint32)(d)) << 24) | (((uint32)(c)) << 16) \
        | (((uint32)(b)) << 8) | ((uint32)(a)))

struct riff_struct
{
        uint32 riff_id; // 'RIFF'
        uint32 len;
        uint32 wave_id; // 'WAVE'
};

struct chunk_struct
{
        uint32 fourcc;
        uint32 len;
};

struct format_struct
{
        uint16 format_tag;
        uint16 channels;
        uint32 samples_per_sec;
        uint32 avg_bytes_per_sec;
        uint16 block_align;
        uint16 bits_per_sample;
};


struct wave_struct
{
        struct riff_struct riff;
        struct chunk_struct format_chunk;
        struct format_struct format;
        struct chunk_struct data_chunk;
};


RecorderWindow::RecorderWindow()
        :
        BWindow(BRect(XPOS, YPOS, XPOS + MIN_WIDTH, YPOS + MIN_HEIGHT),
                B_TRANSLATE_SYSTEM_NAME("SoundRecorder"), B_TITLED_WINDOW,
                B_ASYNCHRONOUS_CONTROLS | B_NOT_V_RESIZABLE | B_NOT_ZOOMABLE),
        fPlayer(NULL),
        fSoundList(NULL),
        fPlayFile(NULL),
        fPlayTrack(NULL),
        fPlayFrames(0),
        fLooping(false),
        fSavePanel(NULL),
        fInitCheck(B_OK)
{
        fRoster = NULL;
        fRecordButton = NULL;
        fPlayButton = NULL;
        fStopButton = NULL;
        fSaveButton = NULL;
        fLoopButton = NULL;
        fInputField = NULL;
        fRecorder = NULL;
        fRecording = false;
        fExternalConnection = false;
        fTempCount = -1;
        fButtonState = btnPaused;

        CalcSizes(MIN_WIDTH, MIN_HEIGHT);

        fInitCheck = InitWindow();
        if (fInitCheck != B_OK) {
                if (fInitCheck == B_NAME_NOT_FOUND)
                        ErrorAlert(B_TRANSLATE("Cannot find default audio hardware"),
                                fInitCheck);
                else
                        ErrorAlert(B_TRANSLATE("Cannot connect to media server"),
                                fInitCheck);
                PostMessage(B_QUIT_REQUESTED);
        } else
                Show();
}


RecorderWindow::~RecorderWindow()
{
        //  The MediaRecorder have to be deleted, the dtor
        //  disconnect it from the media_kit.
        delete fRecorder;

        delete fPlayer;

        if (fPlayTrack && fPlayFile)
                fPlayFile->ReleaseTrack(fPlayTrack);

        if (fPlayFile)
                delete fPlayFile;
        fPlayTrack = NULL;
        fPlayFile = NULL;

        //      Clean up items in list view.
        if (fSoundList) {
                fSoundList->DeselectAll();
                for (int i = 0; i < fSoundList->CountItems(); i++) {
                        WINDOW((stderr, "clean up item %d\n", i+1));
                        SoundListItem* item = dynamic_cast<SoundListItem *>(fSoundList->ItemAt(i));
                        if (item) {
                                if (item->IsTemp())
                                        item->Entry().Remove(); //      delete temp file
                                delete item;
                        }
                }
                fSoundList->MakeEmpty();
        }
        //      Clean up currently recording file, if any.
        fRecEntry.Remove();
        fRecEntry.Unset();

        delete fSavePanel;
}


status_t
RecorderWindow::InitCheck()
{
        return fInitCheck;
}


void
RecorderWindow::CalcSizes(float min_width, float min_height)
{
        //      Set up size limits based on new screen size
        BScreen screen(this);
        BRect rect = screen.Frame();
        float width = rect.Width() - 12;
        SetSizeLimits(min_width, width, min_height, rect.Height() - 24);

        //      Don't zoom to cover all of screen; user can resize last bit if necessary.
        //      This leaves other windows visible.
        if (width > 640)
                width = 640 + (width - 640) / 2;
        SetZoomLimits(width, rect.Height() - 24);
}


status_t
RecorderWindow::InitWindow()
{
        BPopUpMenu * popup = 0;
        status_t error;

        try {
                //      Find temp directory for recorded sounds.
                BPath path;
                if (!(error = find_directory(B_SYSTEM_TEMP_DIRECTORY, &path)))
                        error = fTempDir.SetTo(path.Path());
                if (error < 0)
                        goto bad_mojo;

                //      Make sure the media roster is there (which means the server is there).
                fRoster = BMediaRoster::Roster(&error);
                if (!fRoster)
                        goto bad_mojo;

                error = fRoster->GetAudioInput(&fAudioInputNode);
                if (error < B_OK) //    there's no input?
                        goto bad_mojo;

                error = fRoster->GetAudioMixer(&fAudioMixerNode);
                if (error < B_OK) //    there's no mixer?
                        goto bad_mojo;

                fRecorder = new BMediaRecorder("Sound Recorder",
                        B_MEDIA_RAW_AUDIO);

                if (fRecorder->InitCheck() < B_OK)
                        goto bad_mojo;

                // Set the node to accept only audio data
                media_format output_format;
                output_format.type = B_MEDIA_RAW_AUDIO;
                output_format.u.raw_audio = media_raw_audio_format::wildcard;
                fRecorder->SetAcceptedFormat(output_format);

                //      Create the window header with controls
                BRect r(Bounds());
                r.bottom = r.top + 175;
                BBox *background = new BBox(r, "_background",
                        B_FOLLOW_LEFT_RIGHT     | B_FOLLOW_TOP, B_WILL_DRAW
                        | B_FRAME_EVENTS | B_NAVIGABLE_JUMP, B_NO_BORDER);

                AddChild(background);

                r = background->Bounds();
                r.left = 0;
                r.right = r.left + 38;
                r.bottom = r.top + 104;
                fVUView = new VUView(r, B_FOLLOW_LEFT|B_FOLLOW_TOP);
                background->AddChild(fVUView);

                r = background->Bounds();
                r.left = r.left + 40;
                r.bottom = r.top + 104;
                fScopeView = new ScopeView(r, B_FOLLOW_LEFT_RIGHT|B_FOLLOW_TOP);
                background->AddChild(fScopeView);

                r = background->Bounds();
                r.left = 2;
                r.right -= 26;
                r.top = 115;
                r.bottom = r.top + 30;
                fTrackSlider = new TrackSlider(r, "trackSlider", new BMessage(POSITION_CHANGED),
                        B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
                background->AddChild(fTrackSlider);

                BRect buttonRect;

                //      Button for rewinding
                buttonRect = BRect(BPoint(0,0), kSkipButtonSize);
                buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-7, 25));
                fRewindButton = new TransportButton(buttonRect, B_TRANSLATE("Rewind"),
                        kSkipBackBitmapBits, kPressedSkipBackBitmapBits,
                        kDisabledSkipBackBitmapBits, new BMessage(REWIND));
                background->AddChild(fRewindButton);

                //      Button for stopping recording or playback
                buttonRect = BRect(BPoint(0,0), kStopButtonSize);
                buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-48, 25));
                fStopButton = new TransportButton(buttonRect, B_TRANSLATE("Stop"),
                        kStopButtonBitmapBits, kPressedStopButtonBitmapBits,
                        kDisabledStopButtonBitmapBits, new BMessage(STOP));
                background->AddChild(fStopButton);

                //      Button for starting playback of selected sound
                BRect playRect(BPoint(0,0), kPlayButtonSize);
                playRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-82, 25));
                fPlayButton = new PlayPauseButton(playRect, B_TRANSLATE("Play"),
                        new BMessage(PLAY), new BMessage(PLAY_PERIOD), ' ', 0);
                background->AddChild(fPlayButton);

                //      Button for forwarding
                buttonRect = BRect(BPoint(0,0), kSkipButtonSize);
                buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-133, 25));
                fForwardButton = new TransportButton(buttonRect, B_TRANSLATE("Forward"),
                        kSkipForwardBitmapBits, kPressedSkipForwardBitmapBits,
                        kDisabledSkipForwardBitmapBits, new BMessage(FORWARD));
                background->AddChild(fForwardButton);

                //      Button to start recording (or waiting for sound)
                buttonRect = BRect(BPoint(0,0), kRecordButtonSize);
                buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-174, 25));
                fRecordButton = new RecordButton(buttonRect, B_TRANSLATE("Record"),
                        new BMessage(RECORD), new BMessage(RECORD_PERIOD));
                background->AddChild(fRecordButton);

                //      Button for saving selected sound
                buttonRect = BRect(BPoint(0,0), kDiskButtonSize);
                buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-250, 21));
                fSaveButton = new TransportButton(buttonRect, B_TRANSLATE("Save"),
                        kDiskButtonBitmapsBits, kPressedDiskButtonBitmapsBits,
                        kDisabledDiskButtonBitmapsBits, new BMessage(SAVE));
                fSaveButton->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP);
                background->AddChild(fSaveButton);

                //      Button Loop
                buttonRect = BRect(BPoint(0,0), kArrowSize);
                buttonRect.OffsetTo(background->Bounds().RightBottom() - BPoint(23, 48));
                fLoopButton = new DrawButton(buttonRect, B_TRANSLATE("Loop"),
                        kLoopArrowBits, kArrowBits, new BMessage(LOOP));
                fLoopButton->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP);
                fLoopButton->SetTarget(this);
                background->AddChild(fLoopButton);

                buttonRect = BRect(BPoint(0,0), kSpeakerIconBitmapSize);
                buttonRect.OffsetTo(background->Bounds().RightBottom() - BPoint(121, 17));
                SpeakerView *speakerView = new SpeakerView(buttonRect,
                        B_FOLLOW_LEFT | B_FOLLOW_TOP);
                speakerView->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP);
                background->AddChild(speakerView);

                buttonRect = BRect(BPoint(0,0), BPoint(84, 19));
                buttonRect.OffsetTo(background->Bounds().RightBottom() - BPoint(107, 20));
                fVolumeSlider = new VolumeSlider(buttonRect, "volumeSlider",
                        B_FOLLOW_LEFT | B_FOLLOW_TOP);
                fVolumeSlider->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP);
                background->AddChild(fVolumeSlider);

                // Button to mask/see sounds list
                buttonRect = BRect(BPoint(0,0), kUpDownButtonSize);
                buttonRect.OffsetTo(background->Bounds().RightBottom() - BPoint(21, 25));
                fUpDownButton = new UpDownButton(buttonRect, new BMessage(VIEW_LIST));
                fUpDownButton->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP);
                background->AddChild(fUpDownButton);

                r = Bounds();
                r.top = background->Bounds().bottom + 1;
                fBottomBox = new BBox(r, "bottomBox", B_FOLLOW_ALL);
                fBottomBox->SetBorder(B_NO_BORDER);
                AddChild(fBottomBox);

                //      The actual list of recorded sounds (initially empty) sits
                //      below the header with the controls.
                r = fBottomBox->Bounds();
                r.left += 190;
                r.InsetBy(10, 10);
                r.left -= 10;
                r.top += 4;
                r.right -= B_V_SCROLL_BAR_WIDTH;
                r.bottom -= 25;
                fSoundList = new SoundListView(r, B_TRANSLATE("Sound List"),
                        B_FOLLOW_ALL);
                fSoundList->SetSelectionMessage(new BMessage(SOUND_SELECTED));
                fSoundList->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
                BScrollView *scroller = new BScrollView("scroller", fSoundList,
                        B_FOLLOW_ALL, 0, false, true, B_FANCY_BORDER);
                fBottomBox->AddChild(scroller);

                r = fBottomBox->Bounds();
                r.right = r.left + 190;
                r.bottom -= 25;
                r.InsetBy(10, 8);
                r.top -= 1;
                fFileInfoBox = new BBox(r, "fileinfo", B_FOLLOW_LEFT);
                fFileInfoBox->SetLabel(B_TRANSLATE("File info"));

                fFileInfoBox->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));

                BFont font = be_plain_font;
                font.SetSize(font.Size() * 0.92f);
                font_height height;
                font.GetHeight(&height);
                float fontHeight = height.ascent + height.leading + height.descent;

                r = fFileInfoBox->Bounds();
                r.left = 8;
                r.top = fontHeight + 6;
                r.bottom = r.top + fontHeight + 3;
                r.right -= 10;
                fFilename = new BStringView(r, "filename", B_TRANSLATE("File name:"));
                fFileInfoBox->AddChild(fFilename);
                fFilename->SetFont(&font, B_FONT_SIZE);
                fFilename->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));

                r.top += fontHeight;
                r.bottom = r.top + fontHeight + 3;
                fFormat = new BStringView(r, "format", B_TRANSLATE("Format:"));
                fFileInfoBox->AddChild(fFormat);
                fFormat->SetFont(&font, B_FONT_SIZE);
                fFormat->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));

                r.top += fontHeight;
                r.bottom = r.top + fontHeight + 3;
                fCompression = new BStringView(r, "compression",
                        B_TRANSLATE("Compression:"));
                fFileInfoBox->AddChild(fCompression);
                fCompression->SetFont(&font, B_FONT_SIZE);
                fCompression->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));

                r.top += fontHeight;
                r.bottom = r.top + fontHeight + 3;
                fChannels = new BStringView(r, "channels", B_TRANSLATE("Channels:"));
                fFileInfoBox->AddChild(fChannels);
                fChannels->SetFont(&font, B_FONT_SIZE);
                fChannels->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));

                r.top += fontHeight;
                r.bottom = r.top + fontHeight + 3;
                fSampleSize = new BStringView(r, "samplesize",
                        B_TRANSLATE("Sample size:"));
                fFileInfoBox->AddChild(fSampleSize);
                fSampleSize->SetFont(&font, B_FONT_SIZE);
                fSampleSize->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));

                r.top += fontHeight;
                r.bottom = r.top + fontHeight + 3;
                fSampleRate = new BStringView(r, "samplerate",
                        B_TRANSLATE("Sample rate:"));
                fFileInfoBox->AddChild(fSampleRate);
                fSampleRate->SetFont(&font, B_FONT_SIZE);
                fSampleRate->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));

                r.top += fontHeight;
                r.bottom = r.top + fontHeight + 3;
                fDuration = new BStringView(r, "duration", B_TRANSLATE("Duration:"));
                fFileInfoBox->AddChild(fDuration);
                fDuration->SetFont(&font, B_FONT_SIZE);
                fDuration->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));

                fFileInfoBox->ResizeTo(fFileInfoBox->Frame().Width(),
                        r.bottom + fontHeight / 2.0f);
                fDeployedHeight = MIN_HEIGHT + fFileInfoBox->Bounds().Height() + 40.0f;

                //      Input selection lists all available physical inputs that produce
                //      buffers with B_MEDIA_RAW_AUDIO format data.
                popup = new BPopUpMenu(B_TRANSLATE("Input"));
                const int maxInputCount = 64;
                dormant_node_info dni[maxInputCount];

                int32 real_count = maxInputCount;

                error = fRoster->GetDormantNodes(dni, &real_count, 0, &output_format,
                        0, B_BUFFER_PRODUCER | B_PHYSICAL_INPUT);
                if (real_count > maxInputCount) {
                        WINDOW((stderr, "dropped %" B_PRId32 " inputs\n", real_count - maxInputCount));
                        real_count = maxInputCount;
                }
                char selected_name[B_MEDIA_NAME_LENGTH] = "Default input";
                BMessage * msg;
                BMenuItem * item;
                for (int i = 0; i < real_count; i++) {
                        msg = new BMessage(INPUT_SELECTED);
                        msg->AddData("node", B_RAW_TYPE, &dni[i], sizeof(dni[i]));
                        item = new BMenuItem(dni[i].name, msg);
                        popup->AddItem(item);
                        media_node_id ni[12];
                        int32 ni_count = 12;
                        error = fRoster->GetInstancesFor(dni[i].addon, dni[i].flavor_id,
                                ni, &ni_count);
                        if (error == B_OK) {
                                for (int j = 0; j < ni_count; j++) {
                                        if (ni[j] == fAudioInputNode.node) {
                                                strcpy(selected_name, dni[i].name);
                                                break;
                                        }
                                }
                        }
                }

                //      Create the actual widget
                r = fFileInfoBox->Bounds();
                r.top = r.bottom + 2;
                r.bottom = r.top + 18;
                r.InsetBy(10, 10);
                fInputField = new BMenuField(r, "Input", B_TRANSLATE("Input:"), popup);
                fInputField->SetDivider(fInputField->StringWidth(B_TRANSLATE("Input:"))
                        + 4.0f);
                fBottomBox->AddChild(fInputField);

                fBottomBox->AddChild(fFileInfoBox);

                fBottomBox->Hide();
                CalcSizes(MIN_WIDTH, MIN_HEIGHT);
                ResizeTo(Frame().Width(), MIN_HEIGHT);

                popup->Superitem()->SetLabel(selected_name);

                // Make sure the save panel is happy.
                fSavePanel = new BFilePanel(B_SAVE_PANEL);
                fSavePanel->SetTarget(this);
        }
        catch (...) {
                goto bad_mojo;
        }
        UpdateButtons();
        return B_OK;

        //      Error handling.
bad_mojo:
        if (error >= 0)
                error = B_ERROR;
        if (fRecorder)
                delete fRecorder;

        delete fPlayer;
        if (!fInputField)
                delete popup;
        return error;
}


bool
RecorderWindow::QuitRequested() //      this means Close pressed
{
        StopRecording();
        StopPlaying();
        be_app->PostMessage(B_QUIT_REQUESTED);
        return true;
}


void
RecorderWindow::MessageReceived(BMessage * message)
{
        //      Your average generic message dispatching switch() statement.
        switch (message->what) {
        case INPUT_SELECTED:
                Input(message);
                break;
        case SOUND_SELECTED:
                Selected(message);
                break;
        case STOP_PLAYING:
                StopPlaying();
                break;
        case STOP_RECORDING:
                StopRecording();
                break;
        case PLAY_PERIOD:
                if (fPlayer) {
                        if (fPlayer->HasData())
                                fPlayButton->SetPlaying();
                        else
                                fPlayButton->SetPaused();
                }
                break;
        case RECORD_PERIOD:
                fRecordButton->SetRecording();
                break;
        case RECORD:
                Record(message);
                break;
        case STOP:
                Stop(message);
                break;
        case PLAY:
                Play(message);
                break;
        case SAVE:
                Save(message);
                break;
        case B_SAVE_REQUESTED:
                DoSave(message);
                break;
        case VIEW_LIST:
                if (fUpDownButton->Value() == B_CONTROL_ON) {
                        fBottomBox->Show();
                        CalcSizes(MIN_WIDTH, fDeployedHeight);
                        ResizeTo(Frame().Width(), fDeployedHeight);
                } else {
                        fBottomBox->Hide();
                        CalcSizes(MIN_WIDTH, MIN_HEIGHT);
                        ResizeTo(Frame().Width(), MIN_HEIGHT);

                }
                break;
        case UPDATE_TRACKSLIDER:
                {
                        bigtime_t timestamp = fPlayTrack->CurrentTime();
                        fTrackSlider->SetMainTime(timestamp, false);
                        fScopeView->SetMainTime(timestamp);
                }
                break;
        case POSITION_CHANGED:
                {
                        bigtime_t right, left, main;
                        if (message->FindInt64("main", &main) == B_OK) {
                                if (fPlayTrack) {
                                        fPlayTrack->SeekToTime(fTrackSlider->MainTime());
                                        fPlayFrame = fPlayTrack->CurrentFrame();
                                }
                                fScopeView->SetMainTime(main);
                        }
                        if (message->FindInt64("right", &right) == B_OK) {
                                if (fPlayTrack) {
                                        fPlayLimit = MIN(fPlayFrames,
                                                (off_t)(right * fPlayFormat.u.raw_audio.frame_rate
                                                        / 1000000LL));
                                }
                                fScopeView->SetRightTime(right);
                        }
                        if (message->FindInt64("left", &left) == B_OK)
                                fScopeView->SetLeftTime(left);
                        break;
                }
        case LOOP:
                fLooping = fLoopButton->ButtonState();
                break;
        case B_SIMPLE_DATA:
        case B_REFS_RECEIVED:
                {
                        RefsReceived(message);
                        break;
                }
        case B_COPY_TARGET:
                CopyTarget(message);
                break;
        default:
                BWindow::MessageReceived(message);
                break;
        }
}


void
RecorderWindow::Record(BMessage * message)
{
        //      User pressed Record button
        fRecording = true;
        if (fButtonState != btnPaused) {
                StopRecording();
                return;                 //      user is too fast on the mouse
        }
        SetButtonState(btnRecording);
        fRecordButton->SetRecording();

        char name[256];
        //      Create a file with a temporary name
        status_t err = NewTempName(name);
        if (err < B_OK) {
                ErrorAlert(B_TRANSLATE("Cannot find an unused name to use for the "
                        "new recording"), err);
                return;
        }
        //      Find the file so we can refer to it later
        err = fTempDir.FindEntry(name, &fRecEntry);
        if (err < B_OK) {
                ErrorAlert(B_TRANSLATE("Cannot find the temporary file created to "
                        "hold the new recording"), err);
                return;
        }
        err = fRecFile.SetTo(&fTempDir, name, O_RDWR);
        if (err < B_OK) {
                ErrorAlert(B_TRANSLATE("Cannot open the temporary file created to "
                        "hold the new recording"), err);
                fRecEntry.Unset();
                return;
        }
        //      Reserve space on disk (creates fewer fragments)
        err = fRecFile.SetSize(4 * fRecordFormat.u.raw_audio.channel_count
                * fRecordFormat.u.raw_audio.frame_rate
                * (fRecordFormat.u.raw_audio.format
                        & media_raw_audio_format::B_AUDIO_SIZE_MASK));
        if (err < B_OK) {
                ErrorAlert(B_TRANSLATE("Cannot record a sound that long"), err);
                fRecEntry.Remove();
                fRecEntry.Unset();
                return;
        }
        fRecSize = 0;

        fRecFile.Seek(sizeof(struct wave_struct), SEEK_SET);

        // Hook up input
        err = MakeRecordConnection(fAudioInputNode);
        if (err < B_OK) {
                ErrorAlert(B_TRANSLATE("Cannot connect to the selected sound input"),
                        err);
                fRecEntry.Remove();
                fRecEntry.Unset();
                return;
        }
        fRecorder->Start();
}


void
RecorderWindow::Play(BMessage * message)
{
        if (fPlayer) {
                //      User pressed Play button and playing
                if (fPlayer->HasData())
                        fPlayButton->SetPaused();
                else
                        fPlayButton->SetPlaying();
                fPlayer->SetHasData(!fPlayer->HasData());
                return;
        }

        SetButtonState(btnPlaying);
        fPlayButton->SetPlaying();

        if (!fPlayTrack) {
                ErrorAlert(B_TRANSLATE("Cannot get the file to play"), B_ERROR);
                return;
        }

        fPlayLimit = MIN(fPlayFrames, (off_t)(fTrackSlider->RightTime()
                * fPlayFormat.u.raw_audio.frame_rate / 1000000LL));
        fPlayTrack->SeekToTime(fTrackSlider->MainTime());
        fPlayFrame = fPlayTrack->CurrentFrame();

        // Create our internal Node which plays sound, and register it.
        fPlayer = new BSoundPlayer(fAudioMixerNode, &fPlayFormat.u.raw_audio,
                "Sound Player");
        status_t err = fPlayer->InitCheck();
        if (err < B_OK)
                return;

        fVolumeSlider->SetSoundPlayer(fPlayer);
        fPlayer->SetCallbacks(PlayFile, NotifyPlayFile, this);

        //      And get it going...
        fPlayer->Start();
        fPlayer->SetHasData(true);
}


void
RecorderWindow::Stop(BMessage * message)
{
        //      User pressed Stop button.
        //      Stop recorder.
        StopRecording();
        //      Stop player.
        StopPlaying();
}


void
RecorderWindow::Save(BMessage * message)
{
        //      User pressed Save button.
        //      Find the item to save.
        int32 index = fSoundList->CurrentSelection();
        SoundListItem* pItem = dynamic_cast<SoundListItem*>(fSoundList->ItemAt(index));
        if ((! pItem) || (pItem->Entry().InitCheck() != B_OK))
                return;

        // Update the save panel and show it.
        char filename[B_FILE_NAME_LENGTH];
        pItem->Entry().GetName(filename);
        BMessage saveMsg(B_SAVE_REQUESTED);
        entry_ref ref;
        pItem->Entry().GetRef(&ref);

        if (saveMsg.AddPointer("sound list item", pItem) != B_OK)
                fprintf(stderr, "failed to add pItem\n");
        fSavePanel->SetSaveText(filename);
        fSavePanel->SetMessage(&saveMsg);
        fSavePanel->Show();
}


void
RecorderWindow::DoSave(BMessage * message)
{
        // User picked a place to put the file.
        // Find the location of the old (e.g.
        // temporary file), and the name of the
        // new file to save.
        entry_ref old_ref, new_dir_ref;
        const char* new_name;
        SoundListItem* pItem;

        if ((message->FindPointer("sound list item", (void**) &pItem) == B_OK)
                && (message->FindRef("directory", &new_dir_ref) == B_OK)
                && (message->FindString("name", &new_name) == B_OK)) {
                BEntry& oldEntry = pItem->Entry();
                BFile oldFile(&oldEntry, B_READ_WRITE);
                if (oldFile.InitCheck() != B_OK)
                        return;

                BDirectory newDir(&new_dir_ref);
                if (newDir.InitCheck() != B_OK)
                        return;

                BFile newFile;
                newDir.CreateFile(new_name, &newFile);

                if (newFile.InitCheck() != B_OK)
                        return;

                status_t err = CopyFile(newFile, oldFile);

                if (err == B_OK) {
                        // clean up the sound list and item
                        if (pItem->IsTemp())
                                oldEntry.Remove(); // blows away temp file!
                        oldEntry.SetTo(&newDir, new_name);
                        pItem->SetTemp(false);  // don't blow the new entry away when we exit!
                        fSoundList->Invalidate();
                }
        } else {
                WINDOW((stderr, "Couldn't save file.\n"));
        }
}


void
RecorderWindow::Input(BMessage * message)
{
        //      User selected input from pop-up
        const dormant_node_info * dni = 0;
        ssize_t size = 0;
        if (message->FindData("node", B_RAW_TYPE, (const void **)&dni, &size))
                return;         //      bad input selection message

        media_node_id node_id;
        status_t error = fRoster->GetInstancesFor(dni->addon, dni->flavor_id, &node_id);
        if (error != B_OK)
                fRoster->InstantiateDormantNode(*dni, &fAudioInputNode);
        else
                fRoster->GetNodeFor(node_id, &fAudioInputNode);
}


void
RecorderWindow::Selected(BMessage * message)
{
        //      User selected a sound in list view
        int32 selIdx = fSoundList->CurrentSelection();
        SoundListItem* pItem = dynamic_cast<SoundListItem*>(fSoundList->ItemAt(selIdx));
        if (!pItem)
                return;
        status_t err = UpdatePlayFile(pItem, true);
        if (err != B_OK) {
                ErrorAlert(B_TRANSLATE("Cannot recognize this file as a media file"),
                        err == B_MEDIA_NO_HANDLER ? B_OK : err);
                RemoveCurrentSoundItem();
        }
        UpdateButtons();
}


status_t
RecorderWindow::MakeRecordConnection(const media_node & input)
{
        CONNECT((stderr, "RecorderWindow::MakeRecordConnection()\n"));

        status_t err = B_OK;
        media_output audioOutput;

        if (!fRecorder->IsConnected()) {
                //      Find an available output for the given input node.
                int32 count = 0;
                err = fRoster->GetFreeOutputsFor(input, &audioOutput, 1,
                        &count, B_MEDIA_RAW_AUDIO);

                if (err < B_OK) {
                        CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
                                " couldn't get free outputs from audio input node\n"));
                        return err;
                }

                if (count < 1) {
                        CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
                                " no free outputs from audio input node\n"));
                        return B_BUSY;
                }

                // Get a format, any format.
                fRecordFormat.u.raw_audio = audioOutput.format.u.raw_audio;
                fExternalConnection = false;
        } else {
                fRecordFormat.u.raw_audio = fRecorder->AcceptedFormat().u.raw_audio;
                fExternalConnection = true;
        }

        fRecordFormat.type = B_MEDIA_RAW_AUDIO;

        //      Tell the consumer where we want data to go.
        err = fRecorder->SetHooks(RecordFile, NotifyRecordFile, this);

        if (err < B_OK) {
                CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
                        " couldn't set the sound recorder's hook functions\n"));
                return err;
        }

        if (!fRecorder->IsConnected()) {

                err = fRecorder->Connect(input, &audioOutput, &fRecordFormat);

                if (err < B_OK) {
                        CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
                                " failed to connect sound recorder to audio input node.\n"));

                        fRecorder->SetHooks(NULL, NULL, NULL);
                        return err;
                }
        }

        return B_OK;
}


status_t
RecorderWindow::BreakRecordConnection()
{
        return fRecorder->Disconnect();
}


status_t
RecorderWindow::StopRecording()
{
        if (!fRecording)
                return B_OK;
        fRecording = false;

        status_t err = B_OK;
        err = fRecorder->Stop(true);
        if (err < B_OK)
                return err;

        // We maintain the connection active
        // if the user connected us from Cortex.
        if (!fExternalConnection) {
                BreakRecordConnection();
        }

        fRecorder->SetHooks(NULL, NULL, NULL);

        if (fRecSize > 0) {

                wave_struct header;
                header.riff.riff_id = FOURCC('R','I','F','F');
                header.riff.len = fRecSize + sizeof(header) - 8;
                header.riff.wave_id = FOURCC('W','A','V','E');
                header.format_chunk.fourcc = FOURCC('f','m','t',' ');
                header.format_chunk.len = sizeof(header.format);
                header.format.format_tag = 1;
                header.format.channels = fRecordFormat.u.raw_audio.channel_count;
                header.format.samples_per_sec = (uint32)fRecordFormat.u.raw_audio.frame_rate;
                header.format.avg_bytes_per_sec = (uint32)(fRecordFormat.u.raw_audio.frame_rate
                        * fRecordFormat.u.raw_audio.channel_count
                        * (fRecordFormat.u.raw_audio.format & 0xf));
                header.format.bits_per_sample = (fRecordFormat.u.raw_audio.format & 0xf) * 8;
                header.format.block_align = (fRecordFormat.u.raw_audio.format & 0xf)
                        * fRecordFormat.u.raw_audio.channel_count;
                header.data_chunk.fourcc = FOURCC('d','a','t','a');
                header.data_chunk.len = fRecSize;
                fRecFile.Seek(0, SEEK_SET);
                fRecFile.Write(&header, sizeof(header));

                fRecFile.SetSize(fRecSize + sizeof(header));
                //      We reserve space; make sure we cut off any excess at the end.
                AddSoundItem(fRecEntry, true);
        } else
                fRecEntry.Remove();

        //      We're done for this time.
        fRecEntry.Unset();
        //      Close the file.
        fRecFile.Unset();
        //      No more recording going on.
        fRecSize = 0;
        SetButtonState(btnPaused);
        fRecordButton->SetStopped();

        return B_OK;
}


status_t
RecorderWindow::StopPlaying()
{
        if (fPlayer) {
                fPlayer->Stop();
                fPlayer->SetCallbacks(0, 0, 0);
                fVolumeSlider->SetSoundPlayer(NULL);
                delete fPlayer;
                fPlayer = NULL;
        }
        SetButtonState(btnPaused);
        fPlayButton->SetStopped();
        fTrackSlider->ResetMainTime();
        fScopeView->SetMainTime(*fTrackSlider->MainTime());
        return B_OK;
}


void
RecorderWindow::SetButtonState(BtnState state)
{
        fButtonState = state;
        UpdateButtons();
}


void
RecorderWindow::UpdateButtons()
{
        bool hasSelection = (fSoundList->CurrentSelection() >= 0);
        fRecordButton->SetEnabled(fButtonState != btnPlaying);
        fPlayButton->SetEnabled((fButtonState != btnRecording) && hasSelection);
        fRewindButton->SetEnabled((fButtonState != btnRecording) && hasSelection);
        fForwardButton->SetEnabled((fButtonState != btnRecording) && hasSelection);
        fStopButton->SetEnabled(fButtonState != btnPaused);
        fSaveButton->SetEnabled(hasSelection && (fButtonState != btnRecording));
        fInputField->SetEnabled(fButtonState != btnRecording);
}

#ifndef __HAIKU__
extern "C" status_t DecodedFormat__11BMediaTrackP12media_format(
        BMediaTrack *self, media_format *inout_format);
#endif


status_t
RecorderWindow::UpdatePlayFile(SoundListItem* item, bool updateDisplay)
{
        fScopeView->CancelRendering();
        StopPlaying();
        StopRecording();

        if (fPlayTrack && fPlayFile) {
                fPlayFile->ReleaseTrack(fPlayTrack);
                fPlayTrack = NULL;
        }
        if (fPlayFile) {
                delete fPlayFile;
                fPlayFile = NULL;
        }

        status_t err;
        BEntry& entry = item->Entry();
        entry_ref ref;
        entry.GetRef(&ref);
        fPlayFile = new BMediaFile(&ref); //, B_MEDIA_FILE_UNBUFFERED);
        if ((err = fPlayFile->InitCheck()) < B_OK) {
                delete fPlayFile;
                fPlayFile = NULL;
                return err;
        }

        for (int ix=0; ix < fPlayFile->CountTracks(); ix++) {
                BMediaTrack * track = fPlayFile->TrackAt(ix);
                fPlayFormat.type = B_MEDIA_RAW_AUDIO;
#ifdef __HAIKU__
                if ((track->DecodedFormat(&fPlayFormat) == B_OK)
#else
                if ((DecodedFormat__11BMediaTrackP12media_format(track, &fPlayFormat) == B_OK)
#endif
                        && (fPlayFormat.type == B_MEDIA_RAW_AUDIO)) {
                        fPlayTrack = track;
                        break;
                }
                if (track)
                        fPlayFile->ReleaseTrack(track);
        }

        if (!fPlayTrack) {
                delete fPlayFile;
                fPlayFile = NULL;
                return B_STREAM_NOT_FOUND;
        }

        if (!updateDisplay)
                return B_OK;

        BString filename = B_TRANSLATE("File name: ");
        filename << ref.name;
        fFilename->SetText(filename.String());

        BString format = B_TRANSLATE("Format: ");
        media_file_format file_format;
        if (fPlayFile->GetFileFormatInfo(&file_format) == B_OK)
                format << file_format.short_name;
        BString compression = B_TRANSLATE("Compression: ");
        media_codec_info codec_info;
        if (fPlayTrack->GetCodecInfo(&codec_info) == B_OK) {
                if (strcmp(codec_info.short_name, "raw")==0)
                        compression << B_TRANSLATE("None");
                else
                        compression << codec_info.short_name;
        }
        BString channels = B_TRANSLATE("Channels: ");
        channels << fPlayFormat.u.raw_audio.channel_count;
        BString samplesize = B_TRANSLATE("Sample size: ");
        samplesize << 8 * (fPlayFormat.u.raw_audio.format & 0xf)
                << B_TRANSLATE(" bits");
        BString samplerate = B_TRANSLATE("Sample rate: ");
        samplerate << (int)fPlayFormat.u.raw_audio.frame_rate;
        BString durationString = B_TRANSLATE("Duration: ");
        bigtime_t duration = fPlayTrack->Duration();
        durationString << (float)(duration / 1000000.0) << B_TRANSLATE(" seconds");

        fFormat->SetText(format.String());
        fCompression->SetText(compression.String());
        fChannels->SetText(channels.String());
        fSampleSize->SetText(samplesize.String());
        fSampleRate->SetText(samplerate.String());
        fDuration->SetText(durationString.String());

        fTrackSlider->SetTotalTime(duration, true);
        fScopeView->SetTotalTime(duration, true);
        fScopeView->RenderTrack(fPlayTrack, fPlayFormat);

        fPlayFrames = fPlayTrack->CountFrames();
        return B_OK;
}


void
RecorderWindow::ErrorAlert(const char * action, status_t err)
{
        char msg[300];
        if (err != B_OK)
                sprintf(msg, "%s: %s. [%" B_PRIx32 "]", action, strerror(err), (int32) err);
        else
                sprintf(msg, "%s.", action);
        BAlert* alert = new BAlert("", msg, B_TRANSLATE("Stop"));
        alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
        alert->Go();
}


status_t
RecorderWindow::NewTempName(char * name)
{
        int init_count = fTempCount;
again:
        if (fTempCount-init_count > 25) {
                return B_ERROR;
        }
        else {
                fTempCount++;
                if (fTempCount==0)
                        sprintf(name, "Audio Clip");
                else
                        sprintf(name, "Audio Clip %d", fTempCount);
                BPath path;
                status_t err;
                BEntry tempEnt;
                if ((err = fTempDir.GetEntry(&tempEnt)) < B_OK) {
                        return err;
                }
                if ((err = tempEnt.GetPath(&path)) < B_OK) {
                        return err;
                }
                path.Append(name);
                int fd;
                //      Use O_EXCL so we know we created the file (sync with other instances)
                if ((fd = open(path.Path(), O_RDWR | O_CREAT | O_EXCL, 0666)) < 0) {
                        goto again;
                }
                close(fd);
        }
        return B_OK;
}


void
RecorderWindow::AddSoundItem(const BEntry& entry, bool temp)
{
        //      Create list item to display.
        SoundListItem * listItem = new SoundListItem(entry, temp);
        fSoundList->AddItem(listItem);
        fSoundList->Invalidate();
        fSoundList->Select(fSoundList->IndexOf(listItem));
}


void
RecorderWindow::RemoveCurrentSoundItem() {
        int32 index = fSoundList->CurrentSelection();
        BListItem *item = fSoundList->RemoveItem(index);
        delete item;
        if (index >= fSoundList->CountItems())
                index = fSoundList->CountItems() - 1;
        fSoundList->Select(index);
}


void
RecorderWindow::RecordFile(void* cookie, bigtime_t timestamp,
        void* data, size_t size, const media_format &format)
{
        //      Callback called from the SoundConsumer when receiving buffers.
        RecorderWindow * window = (RecorderWindow *)cookie;

        if (window->fRecording) {
                //      Write the data to file (we don't buffer or guard file access
                //      or anything)
                window->fRecFile.WriteAt(window->fRecSize, data, size);
                window->fVUView->ComputeLevels(data, size, format.u.raw_audio.format);
                window->fRecSize += size;
        }
}


void
RecorderWindow::NotifyRecordFile(void * cookie,
        BMediaRecorder::notification code, ...)
{
        if (code == BMediaRecorder::B_WILL_STOP) {
                RecorderWindow * window = (RecorderWindow *)cookie;
                // Tell the window we've stopped, if it doesn't
                // already know.
                window->PostMessage(STOP_RECORDING);
        }
}


void
RecorderWindow::PlayFile(void * cookie, void * data, size_t size,
        const media_raw_audio_format & format)
{
        //      Callback called from the SoundProducer when producing buffers.
        RecorderWindow * window = (RecorderWindow *)cookie;
        int32 frame_size = (window->fPlayFormat.u.raw_audio.format & 0xf) *
                window->fPlayFormat.u.raw_audio.channel_count;

        if ((window->fPlayFrame < window->fPlayLimit) || window->fLooping) {
                if (window->fPlayFrame >= window->fPlayLimit) {
                        bigtime_t left = window->fTrackSlider->LeftTime();
                        window->fPlayTrack->SeekToTime(&left);
                        window->fPlayFrame = window->fPlayTrack->CurrentFrame();
                }
                int64 frames = 0;
                window->fPlayTrack->ReadFrames(data, &frames);
                window->fVUView->ComputeLevels(data, size / frame_size, format.format);
                window->fPlayFrame += size/frame_size;
                window->PostMessage(UPDATE_TRACKSLIDER);
        } else {
                //      we're done!
                window->PostMessage(STOP_PLAYING);
        }
}


void
RecorderWindow::NotifyPlayFile(void * cookie,
        BSoundPlayer::sound_player_notification code, ...)
{
        if ((code == BSoundPlayer::B_STOPPED) || (code == BSoundPlayer::B_SOUND_DONE)) {
                RecorderWindow * window = (RecorderWindow *)cookie;
                // tell the window we've stopped, if it doesn't
                // already know.
                window->PostMessage(STOP_PLAYING);
        }
}


void
RecorderWindow::RefsReceived(BMessage *msg)
{
        entry_ref ref;
        int32 i = 0;
        int32 countGood = 0;
        int32 countBad = 0;

        while (msg->FindRef("refs", i++, &ref) == B_OK) {

                BEntry entry(&ref, true);
                BPath path(&entry);
                BNode node(&entry);

                if (node.IsFile()) {
                        SoundListItem * listItem = new SoundListItem(entry, false);
                        if (UpdatePlayFile(listItem) == B_OK) {
                                fSoundList->AddItem(listItem);
                                countGood++;
                                continue;
                        }
                        delete listItem;
                } else if(node.IsDirectory()) {

                }
                countBad++;
        }

        if (countBad == 1 && countGood == 0) {
                BAlert* alert = new BAlert(B_TRANSLATE("Nothing to play"),
                        B_TRANSLATE("The file doesn't appear to be an audio file."),
                        B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
                alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
                alert->Go();
        } else if (countBad > 0 && countGood == 0) {
                BAlert* alert = new BAlert(B_TRANSLATE("Nothing to play"),
                        B_TRANSLATE("None of the files appear to be audio files."),
                        B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
                alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
                alert->Go();
        } else if (countGood > 0) {
                if (countBad > 0) {
                        BAlert* alert = new BAlert(B_TRANSLATE("Invalid audio files"),
                        B_TRANSLATE("Some of the files don't appear to be audio files."),
                                B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
                                B_WARNING_ALERT);
                        alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
                        alert->Go();
                }
                fSoundList->Select(fSoundList->CountItems() - 1);
        }
}


void
RecorderWindow::CopyTarget(BMessage *msg)
{
        const char *type = NULL;
        if (msg->FindString("be:types", &type) == B_OK) {
                if (!strcasecmp(type, B_FILE_MIME_TYPE)) {
                        const char *name;
                        entry_ref dir;
                        if (msg->FindString("be:filetypes") != NULL
                                && msg->FindString("name", &name) == B_OK
                                && msg->FindRef("directory", &dir) == B_OK) {
                                BDirectory directory(&dir);
                                BFile file(&directory, name, O_RDWR | O_TRUNC);

                                // seek time
                                bigtime_t start = fTrackSlider->LeftTime();

                                // write data
                                bigtime_t diffTime = fTrackSlider->RightTime()
                                        - fTrackSlider->LeftTime();
                                int64 framesToWrite = (int64) (diffTime
                                        * fPlayFormat.u.raw_audio.frame_rate / 1000000LL);
                                int32 frameSize = (fPlayFormat.u.raw_audio.format & 0xf)
                                        * fPlayFormat.u.raw_audio.channel_count;

                                wave_struct header;
                                header.riff.riff_id = FOURCC('R','I','F','F');
                                header.riff.len
                                        = (frameSize * framesToWrite) + sizeof(header) - 8;
                                header.riff.wave_id = FOURCC('W','A','V','E');
                                header.format_chunk.fourcc = FOURCC('f','m','t',' ');
                                header.format_chunk.len = sizeof(header.format);
                                header.format.format_tag = 1;
                                header.format.channels = fPlayFormat.u.raw_audio.channel_count;
                                header.format.samples_per_sec
                                        = (uint32)fPlayFormat.u.raw_audio.frame_rate;
                                header.format.avg_bytes_per_sec
                                        = (uint32)(fPlayFormat.u.raw_audio.frame_rate
                                        * fPlayFormat.u.raw_audio.channel_count
                                        * (fPlayFormat.u.raw_audio.format & 0xf));
                                header.format.bits_per_sample
                                        = (fPlayFormat.u.raw_audio.format & 0xf) * 8;
                                header.format.block_align = frameSize;
                                header.data_chunk.fourcc = FOURCC('d','a','t','a');
                                header.data_chunk.len = frameSize * framesToWrite;
                                file.Seek(0, SEEK_SET);
                                file.Write(&header, sizeof(header));

                                char *data = (char *)malloc(fPlayFormat.u.raw_audio.buffer_size);

                                fPlayTrack->SeekToTime(&start);
                                fPlayFrame = fPlayTrack->CurrentFrame();
                                while (framesToWrite > 0) {
                                        int64 frames = 0;
                                        status_t err = fPlayTrack->ReadFrames(data, &frames);
                                        if (frames <= 0 || err != B_OK) {
                                                if (err != B_OK)
                                                        fprintf(stderr, "CopyTarget: ReadFrames failed\n");
                                                break;
                                        }
                                        file.Write(data, frames * frameSize);
                                        framesToWrite -= frames;
                                }

                                file.Sync();
                                free(data);
                                BNodeInfo nodeInfo(&file);
                                // set type
                        }
                } else {

                }
        }
}