/*
* Copyright (c) 2013 Hypertriton, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Vislak project.
*/
#include
#include "vs_gui.h"
#include "icons.h"
#include
#include
#include
/* Load audio stream. */
static int
LoadAudio(VS_Clip *v)
{
VS_Project *vsp = v->proj;
sf_count_t nReadFrames = 0;
sf_count_t i, j, frames_px, pos;
int tri = 0, tflag = 0;
if (v->sndFile != NULL) {
sf_close(v->sndFile);
Free(v->sndBuf); v->sndBuf = NULL;
Free(v->sndViz); v->sndViz = NULL;
v->sndVizFrames = 0;
}
memset(&v->sndInfo, 0, sizeof(v->sndInfo));
v->sndPos = 0;
v->sndFile = sf_open(v->audioFile, SFM_READ, &v->sndInfo);
if (v->sndFile == NULL) {
AG_SetError("Cannot open %s", v->audioFile);
return (-1);
}
if ((v->sndBuf = AG_TryMalloc(v->sndInfo.frames*v->sndInfo.channels*
sizeof(float))) == NULL) {
goto fail;
}
nReadFrames = 0;
while (nReadFrames < v->sndInfo.frames) {
sf_count_t rv;
rv = sf_readf_float(v->sndFile, &v->sndBuf[nReadFrames], 4096);
if (rv == 0) {
break;
}
nReadFrames += rv;
}
/* Compute the approximate number of audio samples per video frame */
v->samplesPerFrame = v->sndInfo.samplerate/vsp->frameRate;
/* Compute a reduced waveform for visualization purposes. */
if (sf_command(v->sndFile, SFC_CALC_SIGNAL_MAX, &v->sndPeakSignal,
sizeof(v->sndPeakSignal)) != 0) {
v->sndPeakSignal = 20000.0;
}
frames_px = v->samplesPerFrame/vsp->thumbSz;
v->sndVizFrames = v->sndInfo.frames/frames_px;
if ((v->sndViz = AG_TryMalloc(v->sndVizFrames*sizeof(float))) == NULL) {
goto fail;
}
for (i = 0; i < v->sndVizFrames; i++) {
v->sndViz[i] = 0.0;
}
v->sndPeakSignal /= 10000.0;
for (i = 0, pos = 0;
i < v->sndVizFrames && (pos + frames_px*v->sndInfo.channels) < nReadFrames;
i++) {
for (j = 0; j < frames_px*v->sndInfo.channels; j++) {
v->sndViz[i] += MAX(v->sndViz[i],
fabs(v->sndBuf[pos++]/v->sndPeakSignal));
}
v->sndViz[i] /= frames_px*v->sndInfo.channels;
}
VS_Status(vsp,
_("Audio import successful (%d-Ch, %dHz, %lu frames)"),
v->sndInfo.channels,
v->sndInfo.samplerate,
(Ulong)nReadFrames);
return (0);
fail:
sf_close(v->sndFile);
v->sndFile = NULL;
Free(v->sndBuf);
Free(v->sndViz);
return (-1);
}
/* Load a clip's video frames. */
static int
LoadVideoFrames(VS_Clip *v)
{
VS_Project *vsp = v->proj;
char path[AG_PATHNAME_MAX];
AG_Dir *d;
Uint i;
int fileOk;
vsp->gui.progress.min = 0;
vsp->gui.progress.max = 0;
vsp->gui.progress.val = 0;
AG_MutexLock(&v->lock);
if ((d = AG_OpenDir(v->dir)) == NULL) {
AG_MutexUnlock(&v->lock);
return (-1);
}
vsp->gui.progress.val = v->fileFirst;
vsp->gui.progress.max = (v->fileLast != -1) ? v->fileLast : d->nents;
AG_CloseDir(d);
i = v->fileFirst;
do {
Snprintf(path, sizeof(path), v->fileFmt, v->dir, i);
if (AG_FileExists(path) == -1) {
fileOk = 0;
} else {
AG_MutexUnlock(&v->lock);
VS_Status(vsp, _("Importing: %s"), path);
fileOk = (VS_ClipAddFrame(v, path) == 0) ? 1 : 0;
AG_MutexLock(&v->lock);
}
vsp->gui.progress.val++;
i++;
} while (fileOk && (v->fileLast == -1 || i < v->fileLast));
if (i > 0) { v->x = 1; }
VS_Status(vsp, _("Loaded %u video frames"), v->n);
vsp->gui.progress.val = vsp->gui.progress.max;
AG_MutexUnlock(&v->lock);
return (0);
}
void
VS_ProjectRunOperation(VS_Project *vsp, VS_ProcOp op)
{
AG_ObjectLock(vsp);
vsp->procOp = op;
AG_ObjectUnlock(vsp);
}
/* Process one output frame while in recording mode. */
static void
ProcessRecording(VS_Project *vsp)
{
char pathIn[AG_PATHNAME_MAX];
char pathOut[AG_PATHNAME_MAX];
VS_Clip *vIn = vsp->input;
VS_Clip *vOut = vsp->output;
Uint i;
AG_MutexLock(&vOut->lock);
AG_MutexLock(&vIn->lock);
if (VS_ClipCopyFrame(vOut, vIn, vIn->x) == -1) {
goto stop;
}
/* Format: %s,%u */
snprintf(pathIn, sizeof(pathIn), vIn->fileFmt,
vIn->dir, (vIn->x + 1));
snprintf(pathOut, sizeof(pathOut), vOut->fileFmt,
vOut->dir, (vOut->n - 1));
relink:
if (link(pathIn, pathOut) == -1) {
if (errno == EEXIST) {
unlink(pathOut);
goto relink;
}
AG_SetError("link(%s -> %s): %s", pathIn, pathOut,
AG_Strerror(errno));
goto stop;
}
AG_MutexUnlock(&vIn->lock);
AG_MutexUnlock(&vOut->lock);
return;
stop:
VS_Status(vsp, _("Recording interrupted: %s"), AG_GetError());
vsp->flags &= ~(VS_PROJECT_RECORDING);
AG_MutexUnlock(&vIn->lock);
AG_MutexUnlock(&vOut->lock);
}
/*
* Processing thread for asynchronous per-project operations.
*/
static void *
ProcessThread(void *pProj)
{
VS_Project *vsp = pProj;
Uint32 t1, t2 = 0;
int rCur = 0, delta;
t1 = AG_GetTicks();
for (;;) {
VS_Clip *vIn, *vOut;
t2 = AG_GetTicks();
AG_ObjectLock(vsp);
vIn = vsp->input;
vOut = vsp->output;
if (vsp->procOp == VS_PROC_IDLE && /* Video update */
(t2 - t1) >= (1000/vsp->frameRate)) {
if (vsp->flags & VS_PROJECT_RECORDING) {
ProcessRecording(vsp);
}
vOut->samplesPerFrame = vOut->sndInfo.samplerate /
vsp->frameRate;
/* Process frame movement */
if (vIn->xVel < -1.0 ||
vIn->xVel > +1.0) { /* >=1 frame */
delta = (int)vIn->xVel;
if ((vIn->x+delta) < vIn->n) {
vIn->x += (int)vIn->xVel;
}
} else if (vIn->xVel != 0.0) { /* Sub-frame */
vIn->xVelCur += vIn->xVel;
if (vIn->xVelCur <= -1.0 ||
vIn->xVelCur >= 1.0) {
delta = (vIn->xVelCur < 0) ? -1 : 1;
vIn->xVelCur = 0.0;
if ((vIn->x+delta) < vIn->n)
vIn->x += delta;
}
}
VS_PlayerUpdate(vsp->gui.playerIn);
VS_PlayerUpdate(vsp->gui.playerOut);
if (vsp->flags & VS_PROJECT_RECORDING) {
if (vOut->n > 1)
vOut->x++;
}
/* Update the effective refresh rate */
t1 = AG_GetTicks();
rCur = (1000/vsp->frameRate) - (t1-t2);
if (rCur < 1) { rCur = 1; }
} else {
switch (vsp->procOp) {
case VS_PROC_LOAD_VIDEO:
AG_ObjectUnlock(vsp);
if (LoadVideoFrames(vIn) == -1) {
VS_Status(vsp,
_("Video import failed: %s"),
AG_GetError());
}
AG_ObjectLock(vsp);
vsp->procOp = (vIn->audioFile != NULL) ?
VS_PROC_LOAD_AUDIO :
VS_PROC_IDLE;
break;
case VS_PROC_LOAD_AUDIO:
AG_ObjectUnlock(vsp);
if (LoadAudio(vOut) == -1) {
VS_Status(vsp,
_("Audio import failed: %s"),
AG_GetError());
}
AG_ObjectLock(vsp);
vsp->procOp = VS_PROC_IDLE;
break;
case VS_PROC_TERMINATE:
VS_Status(vsp, _("Terminating"));
vsp->procOp = VS_PROC_INIT;
AG_ObjectUnlock(vsp);
goto out;
default:
AG_ObjectUnlock(vsp);
AG_Delay(1);
AG_ObjectLock(vsp);
break;
}
}
AG_ObjectUnlock(vsp);
}
out:
AG_ThreadExit(NULL);
}
/*
* Load video stream.
*/
static void
LoadVideoJPEG(AG_Event *event)
{
VS_Clip *v = AG_PTR(1);
char *path = AG_STRING(2);
char *s;
AG_MutexLock(&v->lock);
Free(v->dir);
v->dir = Strdup(path);
if ((s = strrchr(v->dir, PATHSEPC)) != NULL) {
*s = '\0';
}
VS_ProjectRunOperation(v->proj, VS_PROC_LOAD_VIDEO);
AG_MutexUnlock(&v->lock);
}
static void
LoadVideoDlg(AG_Event *event)
{
VS_Clip *v = AG_PTR(1);
AG_Window *win;
AG_FileDlg *fd;
win = AG_WindowNew(0);
AG_WindowSetCaption(win, _("Import video..."));
fd = AG_FileDlgNewMRU(win, "vislak.mru.video",
AG_FILEDLG_LOAD|AG_FILEDLG_CLOSEWIN|AG_FILEDLG_EXPAND);
AG_FileDlgAddType(fd, _("JPEG (select first frame)"), "*.jpg,*.jpeg",
LoadVideoJPEG, "%p", v);
AG_WindowShow(win);
}
/*
* Load audio stream.
*/
static void
LoadAudioFile(AG_Event *event)
{
VS_Clip *v = AG_PTR(1);
char *path = AG_STRING(2);
AG_MutexLock(&v->lock);
Free(v->audioFile);
v->audioFile = Strdup(path);
VS_ProjectRunOperation(v->proj, VS_PROC_LOAD_AUDIO);
AG_MutexUnlock(&v->lock);
}
static void
LoadAudioDlg(AG_Event *event)
{
char extn[64];
VS_Clip *v = AG_PTR(1);
AG_Window *win;
AG_FileDlg *fd;
SF_FORMAT_INFO fmt;
int k, count;
win = AG_WindowNew(0);
AG_WindowSetCaption(win, _("Import audio..."));
fd = AG_FileDlgNewMRU(win, "vislak.mru.audio",
AG_FILEDLG_LOAD|AG_FILEDLG_CLOSEWIN|AG_FILEDLG_EXPAND);
sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int));
for (k = 0; k < count; k++) {
fmt.format = k;
sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fmt, sizeof(fmt));
extn[0] = '.';
extn[1] = '\0';
Strlcat(extn, fmt.extension, sizeof(extn));
AG_FileDlgAddType(fd, fmt.name, extn,
LoadAudioFile, "%p", v);
/* Map extra extensions */
if (strcmp("OGG (OGG Container format)", fmt.name) == 0) {
AG_FileDlgAddType(fd, fmt.name, ".ogg",
LoadAudioFile, "%p", v);
}
}
AG_WindowShow(win);
}
static void
SaveToMPEG4(AG_Event *event)
{
// VS_Clip *v = AG_PTR(1);
// char *path = AG_STRING(2);
/* TODO */
/* VS_ClipSetArchivePath(v, path); */
}
static void
SaveVideoDlg(AG_Event *event)
{
VS_Clip *v = AG_PTR(1);
AG_Window *win;
AG_FileDlg *fd;
win = AG_WindowNew(0);
AG_WindowSetCaption(win, _("Export video as..."));
fd = AG_FileDlgNewMRU(win, "vislak.mru.videos",
AG_FILEDLG_SAVE|AG_FILEDLG_CLOSEWIN|AG_FILEDLG_EXPAND);
AG_FileDlgSetOptionContainer(fd, AG_BoxNewVert(win, AG_BOX_HFILL));
AG_FileDlgAddType(fd, _("MPEG-4"), "*.mpeg", SaveToMPEG4, "%p", v);
AG_WindowShow(win);
}
VS_Project *
VS_ProjectNew(void *parent, const char *name)
{
VS_Project *vsp;
if ((vsp = TryMalloc(sizeof(VS_Project))) == NULL) {
return (NULL);
}
AG_ObjectInitNamed(vsp, &vsProjectClass, name);
if ((vsp->input = VS_ClipNew(vsp)) == NULL ||
(vsp->output = VS_ClipNew(vsp)) == NULL) {
goto fail;
}
AG_ObjectAttach(parent, vsp);
return (vsp);
fail:
AG_ObjectDestroy(vsp);
return (NULL);
}
void
VS_Status(void *obj, const char *fmt, ...)
{
va_list ap;
AG_Label *lbl;
char *s;
if (AG_OfClass(obj, "AG_Widget:VS_View")) {
lbl = ((VS_View *)obj)->clip->proj->gui.status;
} else if (AG_OfClass(obj, "VS_Project")) {
lbl = ((VS_Project *)obj)->gui.status;
} else {
return;
}
va_start(ap, fmt);
Vasprintf(&s, fmt, ap);
va_end(ap);
AG_LabelTextS(lbl, s);
free(s);
}
static void
OnAttach(AG_Event *event)
{
VS_Project *vsp = AG_SELF();
AG_ThreadCreate(&vsp->procTh, ProcessThread, vsp);
}
static void
OnDetach(AG_Event *event)
{
VS_Project *vsp = AG_SELF();
vsp->procOp = VS_PROC_TERMINATE;
while (vsp->procOp != VS_PROC_INIT) {
AG_ObjectUnlock(vsp);
AG_Delay(10);
AG_ObjectLock(vsp);
}
}
static void
Init(void *obj)
{
VS_Project *vsp = obj;
vsp->flags = 0;
vsp->thumbSz = 128;
vsp->waveSz = 64;
vsp->frameRate = 30;
vsp->bendSpeed = 2.0;
vsp->bendSpeedMax = 40.0;
vsp->input = NULL;
vsp->output = NULL;
vsp->procOp = VS_PROC_INIT;
vsp->gui.progress.val = 0;
vsp->gui.progress.min = 0;
vsp->gui.progress.max = 0;
vsp->gui.playerIn = NULL;
vsp->gui.playerOut = NULL;
vsp->gui.status = NULL;
AG_SetEvent(vsp, "attached", OnAttach, NULL);
AG_SetEvent(vsp, "detached", OnDetach, NULL);
}
static void
Destroy(void *obj)
{
VS_Project *vsp = obj;
if (vsp->input != NULL)
VS_ClipDestroy(vsp->input);
if (vsp->output != NULL)
VS_ClipDestroy(vsp->output);
}
static int
Load(void *obj, AG_DataSource *ds, const AG_Version *ver)
{
VS_Project *vsp = obj;
vsp->flags &= ~(VS_PROJECT_SAVED);
vsp->flags |= (AG_ReadUint32(ds) & VS_PROJECT_SAVED);
vsp->thumbSz = (int)AG_ReadUint16(ds);
vsp->waveSz = (int)AG_ReadUint16(ds);
vsp->frameRate = (int)AG_ReadUint8(ds);
vsp->bendSpeed = AG_ReadDouble(ds);
vsp->bendSpeedMax = AG_ReadDouble(ds);
return (0);
}
static int
Save(void *obj, AG_DataSource *ds)
{
VS_Project *vsp = obj;
AG_WriteUint32(ds, vsp->flags & VS_PROJECT_SAVED);
AG_WriteUint16(ds, (Uint16)vsp->thumbSz);
AG_WriteUint16(ds, (Uint16)vsp->waveSz);
AG_WriteUint8(ds, (Uint8)vsp->frameRate);
AG_WriteDouble(ds, vsp->bendSpeed);
AG_WriteDouble(ds, vsp->bendSpeedMax);
return (0);
}
static void *
Edit(void *obj)
{
VS_Project *vsp = obj;
VS_Clip *vIn = vsp->input;
VS_Clip *vOut = vsp->output;
AG_Mutex *lock = &OBJECT(vsp)->lock;
AG_Window *win;
AG_Box *boxStatus, *boxParams;
AG_Statusbar *sb;
AG_ProgressBar *pb;
AG_Numerical *num;
AG_Notebook *nb;
AG_NotebookTab *ntab;
AG_Label *lbl;
AG_Menu *menu;
AG_Pane *pa, *paHoriz;
AG_MenuItem *m, *mNode;
VS_View *vv;
if ((win = AG_WindowNew(AG_WINDOW_MAIN)) == NULL) {
return (NULL);
}
AG_WindowSetCaption(win, _("Vislak <%s>"), OBJECT(vsp)->name);
menu = AG_MenuNew(win, AG_MENU_HFILL);
paHoriz = AG_PaneNewHoriz(win, AG_PANE_EXPAND);
pa = AG_PaneNewVert(paHoriz->div[0], AG_PANE_EXPAND);
{
AG_LabelNewPolled(pa->div[0], AG_LABEL_HFILL,
_("Input (%u frames):"), &vIn->n);
vv = VS_ViewNew(pa->div[0], VS_VIEW_NOAUDIO|VS_VIEW_EXPAND, vIn);
AG_WidgetFocus(vv);
}
{
AG_LabelNewPolled(pa->div[0], AG_LABEL_HFILL,
_("Output (%u frames):"), &vOut->n);
vv = VS_ViewNew(pa->div[1], VS_VIEW_EXPAND, vOut);
}
nb = AG_NotebookNew(paHoriz->div[1], AG_NOTEBOOK_EXPAND);
ntab = AG_NotebookAddTab(nb, _("Input Stream"), AG_BOX_VERT);
vsp->gui.playerIn = VS_PlayerNew(ntab, VS_PLAYER_EXPAND, vIn);
ntab = AG_NotebookAddTab(nb, _("Output Stream"), AG_BOX_VERT);
vsp->gui.playerOut = VS_PlayerNew(ntab, VS_PLAYER_EXPAND, vOut);
boxStatus = AG_BoxNewHoriz(win, AG_BOX_HFILL);
{
sb = AG_StatusbarNew(boxStatus, AG_STATUSBAR_HFILL);
vsp->gui.status = AG_StatusbarAddLabel(sb, "OK");
boxParams = AG_BoxNewVert(boxStatus, AG_BOX_VFILL);
{
AG_NumericalNewDblR(boxParams, 0, NULL,
_("Bend: "), &vsp->bendSpeed, 1.0, vsp->bendSpeedMax);
AG_NumericalNewIntR(boxParams, 0, NULL,
_("FPS: "), &vsp->frameRate, 1, 60);
}
AG_SeparatorNewVert(boxStatus);
lbl = AG_LabelNewPolled(boxStatus, 0,
"FPS=%i\n"
"Drift=%i\n",
&vsp->frameRate, &vOut->drift);
AG_LabelSizeHint(lbl, 2, "");
AG_SeparatorNewVert(boxStatus);
pb = AG_ProgressBarNew(boxStatus, AG_PROGRESS_BAR_HORIZ,
AG_PROGRESS_BAR_SHOW_PCT);
AG_BindInt(pb, "value", &vsp->gui.progress.val);
AG_BindInt(pb, "min", &vsp->gui.progress.min);
AG_BindInt(pb, "max", &vsp->gui.progress.max);
AG_ProgressBarSetWidth(pb, agTextFontHeight*2);
AG_ProgressBarSetLength(pb, 200);
}
/*
* Main application menu
*/
m = AG_MenuNode(menu->root, _("File"), NULL);
{
AG_MenuAction(m, _("Load video stream..."), agIconLoad.s,
LoadVideoDlg, "%p", vIn);
AG_MenuAction(m, _("Load audio stream..."), agIconLoad.s,
LoadAudioDlg, "%p", vOut);
AG_MenuSeparator(m);
AG_MenuAction(m, _("Save video as..."), agIconSave.s,
SaveVideoDlg, "%p", vOut);
}
m = AG_MenuNode(menu->root, _("Edit"), NULL);
{
AG_MenuUintFlagsMp(m, _("Key learn mode"), vsIconControls.s,
&vsp->flags, VS_PROJECT_LEARNING, 0, &OBJECT(vsp)->lock);
}
m = AG_MenuNode(menu->root, _("MIDI"), NULL);
{
VS_MidiDevicesMenu(vIn->midi, m, VS_MIDI_INPUT);
VS_MidiDevicesMenu(vIn->midi, m, VS_MIDI_OUTPUT);
}
AG_PaneMoveDividerPct(paHoriz, 60);
AG_WindowSetGeometryAlignedPct(win, AG_WINDOW_MC, 65, 50);
return (win);
}
AG_ObjectClass vsProjectClass = {
"VS_Project",
sizeof(VS_Project),
{ 0,0 },
Init,
NULL, /* freeData */
Destroy,
Load,
Save,
Edit
};