amigazen AWeb 3 AmigaPython ToolKit unsui FastForward Engi Zen
FastForward

Developer’s Guide
fastforward.library Application Developer Guide
Movie API, Graph API, BOOPSI gadgets, and application integration

FastForward documentation
Overview  |  User’s Guide  |  Developer’s Guide  |  Plugin Developer’s Guide

GitHub
https://github.com/amigazen/FastForward


This guide walks application developers through the practical use of fastforward.library V1. It accompanies the function reference (SDK/Doc/fastforward.doc), the Plugin Developer’s Guide, and the architectural design notes (Docs/ARCHITECTURE.md).

If you are new to the engine, the high-level conceptual model is:

• A Movie is a high-level handle to one media stream (a file, a
URL, a memory buffer, an application-supplied byte stream). Movies are the player-app surface — you Open one, Play it, Wait for events on its signal mask, and Close it. Created with OpenFFMovieA() and disposed with CloseFFMovie().

• A Track is one substream inside a movie (audio track 0, video
track 0, subtitle stream, MIDI track, ...). Tracks are obtained via GetFFTrack(); per-track mute, volume, language and custom sinks are set via SetFFTrackAttrsA().

• A Graph is the low-level filter-graph surface for editors,
transcoders and sequencers. A graph is a set of Elements linked by their typed Pads. Created with CreateFFGraphA() and disposed with DeleteFFGraph().

• An Element is one stage in the graph - a source, a demuxer, a
codec, a filter, a mixer, a sink. Elements are instances of registered Plugins.

• A Pad is a typed endpoint on an Element: src for outputs,
sink for inputs; demuxers add per-track pads named audio, video, subtitle, data. LinkFFPads() wires them.

• A Clock is the timing reference shared by every Element in a
Graph. Backends: FFCK_TIMER (timer.device, default), FFCK_REALTIME (realtime.library Conductor for sequencers / NLE), FFCK_AUDIO (slave to the audio sink's sample clock) and FFCK_EXTERNAL (driven by the application).

• A Plugin is a LoadSeg module that implements one pipeline stage (source, demux, codec, filter, sink). Applications select plugins indirectly through sniffing; plugin authors see the Plugin Developer’s Guide.

The library coexists with datatypes.library and mediatypes.library. Still images (PNG, ILBM, JPEG, GIF, etc.) belong on datatypes / mediatypes; FastForward is the engine for timed media (audio, video, subtitles, MIDI) and the streaming / async / pipeline plumbing those need.


Table of contents

1. Getting started
2. Movie API: a player in one screen
3. Tape-deck transport
4. Track introspection and per-track control
5. Graph API: editors, sequencers, transcoders
6. Custom sinks: rendering inside your app
7. Render targets and video output
8. Clock and synchronisation
9. Events
10. Metadata
11. Media probe (file managers, browsers)
12. Thumbnails and still images
13. Errors and cancellation
14. Per-element tasks and Conductor synchronisation
15. player.gadget and playback.gadget
16. mediatypes.library integration
17. Reference: enums, error codes, transport rates
Appendices:

A. Quick recipes
B. Integration cheatsheet

1. Getting started

1.1 Opening the library

#include <libraries/fastforward.h>
#include <proto/fastforward.h>

struct Library *FastForwardBase = NULL;

FastForwardBase = FF_OpenLibrary(FF_API_VERSION);
if (FastForwardBase == NULL) {
    /* fall back to datatypes.library, or fail */
}

LoadFFPlugins(NULL);    /* uses the resolved default plugin directory */
...
UnloadFFPlugins();
FF_CloseLibrary(FastForwardBase);

FF_OpenLibrary() is a convenience macro defined in <libraries/fastforward.h> that expands to OpenLibrary("fastforward.library", v). The library base is implicit at every LVO trap (same model as exec.library, intuition.library, datatypes.library); the #pragma libcall lines in <pragmas/fastforward_pragmas.h> load the global FastForwardBase into A6 before each call.

A version that does not satisfy FF_API_VERSION means an older library or none installed.

1.2 Build / link

• Headers
Source/include/libraries/fastforward.h - types, tags, opaque
handles

Source/include/libraries/ffplugin.h - plugin ABI, buffer
types, events

Source/include/fastforward/ff_c2p.h - chunky-to-planar helper
Source/include/fastforward/ff_string.h - inline counted-buffer
helpers

• Prototypes / pragmas
Source/include/clib/fastforward_protos.h
Source/include/pragmas/fastforward_pragmas.h
Source/include/proto/fastforward.h (pulls in both)
• No amiga.lib varargs glue is required - the public API uses
*A-suffix tag-list entry points, and SAS/C 6.x gets the matching #pragma tagcall varargs forms; gcc / vbcc users get compound-literal macros in <clib/fastforward_protos.h>.

1.3 Library layout on disk

LIBS:fastforward.library
LIBS:FastForward/Plugins/file.source
LIBS:FastForward/Plugins/pcm.demux
LIBS:FastForward/Plugins/ahi.sink
LIBS:FastForward/Plugins/...
ENV:FastForward/Plugins                 (override default plugin dir)
FastForward:                            (logical assign; optional)

LoadFFPlugins(NULL) resolves the default plugin directory once at library-open time using:

1. ENV variable FastForward/Plugins
2. logical assign FastForward: (plugins live in
FastForward:Plugins/)

3. fallback LIBS:FastForward/Plugins
No path is compiled into the library. Packagers and end users move plugins wherever they like by editing the assign or the ENV variable; applications never need to know.

1.4 House style

FastForward is written exclusively against the Amiga ROM / NDK API surface - no libc, no POSIX types:

<string.h> is replaced by inline helpers in
<fastforward/ff_string.h> that wrap CopyMem plus utility.library Stricmp for case-insensitive matches.

struct TimeVal and struct TimeRequest from <devices/timer.h>
(with __USE_NEW_TIMEVAL__) - never POSIX timeval / timerequest.

• Tag lists are walked through utility.library (GetTagData,
FindTagItem, NextTagItem, FilterTagItems).

• Signals not callbacks. Apps Wait() on the movie's signal mask
exactly the way they Wait() on an Intuition IDCMP port.


2. Movie API: a player in one screen

The Movie API is the recommended surface for player applications. It hides demuxer / codec selection, sink plumbing, and pipeline construction behind a single tag-list-driven open call.

struct FFMovie *m;
ULONG mask, signals;
struct FFEvent ev;

struct TagItem mtags[] = {
    { FFMT_Path,     (ULONG)"work:song.wav" },
    { FFMT_AutoPlay, TRUE                   },
    { FFMT_Volume,   0x10000                },
    { TAG_DONE,      0                      }
};

m = OpenFFMovieA(mtags);
if (m == NULL) { /* GetFFLastError() */ exit(20); }

mask = GetFFMovieSignalMask(m);

while (GetFFMovieState(m) == FFMS_PLAYING ||
       GetFFMovieState(m) == FFMS_PREROLL) {
    signals = Wait(mask | SIGBREAKF_CTRL_C);
    if (signals & SIGBREAKF_CTRL_C) {
        StopFFMovie(m);
        break;
    }
    while (PollFFMovieEvent(m, &ev) > 0) {
        if (ev.fe_Kind == FFEV_EOS) goto done;
    }
}
done:
CloseFFMovie(m);

2.1 Source selection

OpenFFMovieA() accepts one source tag from each of these mutually exclusive groups:

Tag(s) Source
FFMT_Path DOS file path
FFMT_FileHandle Already-open BPTR; engine takes ownership
FFMT_URL Network location (http/rtsp/hls) — needs network source plugin
FFMT_MemData + FFMT_MemSize In-memory buffer via built-in mem.source
FFMT_IOCallbacks + FFMT_IOUserData App-supplied read/seek/close (browser cache, RTSP shim)

For network sources FFMT_Timeout, FFMT_UserAgent, FFMT_ProxyServer and FFMT_ProxyPort are forwarded to the underlying source plugin.

2.2 Synchronous vs asynchronous open

OpenFFMovieA() blocks by default - it returns once the source has been opened, format identified and tracks enumerated. For network or slow sources, set FFMT_AsyncOpen = TRUE and FFMT_OpenSignal = sigbit to receive a notification when the open completes; the call returns immediately with the movie in FFMS_OPENED (or FFMS_ERROR).

CancelFFMovie() aborts a hung or in-flight open and posts an FFEV_ERROR event with FFERR_CANCELLED.

2.3 Disposal

CloseFFMovie(m);

CloseFFMovie() tears down the playback Process, flushes all pipeline buffers, closes any sinks the engine created, and frees the movie record itself. Application-supplied sinks (FFMT_AudioSink / FFMT_VideoSink) are detached but not disposed - the caller retains ownership. EjectFFMovie() is a macro alias.



API supplement (from API.md)

FastForward exposes two layers on top of fastforward.library. Both share the same plugin registry, clock subsystem, host services and event queue.

Layer When to use
Movie API Player apps. Opens a file, demuxes, decodes, builds a default audio/video graph, optionally autoplays, exposes tracks, signals progress over an exec signal mask.
Graph API Editors, transcoders, music sequencers, live mixers, anything that needs to wire arbitrary FFElements by their typed FFPads, drive a master FFClock, or replace pieces of a default movie graph.

1. Naming and call conventions

The public API follows AmigaOS conventions, not POSIX/C-library style:

Verb-then-object: OpenFFMovieA, CloseFFMovie, PlayFFMovie, CreateFFGraphA, LinkFFPads. The library tag is baked into the object name (FFMovie, FFGraph, FFElement, FFPad, FFClock) so every public symbol is unambiguous without a blanket FF_ prefix on the verb.
Implicit library base. The base is not an explicit parameter. <proto/fastforward.h> provides #pragma libcall directives that load the global FastForwardBase into A6 before each LVO trap, exactly like exec.library, intuition.library or datatypes.library calls.
A suffix on tag-list entries. OpenFFMovieA(struct TagItem *), GetFFMovieAttrsA(...), CreateFFGraphA(...), etc. SAS/C 6.x also gets the matching varargs convenience form (#pragma tagcall) so OpenFFMovie(FFMT_Path, "...", FFMT_AutoPlay, TRUE, TAG_DONE) Just Works. gcc / vbcc users have equivalent compound-literal macros in <libraries/fastforward.h>.
Internals are explicit. Engine helpers (FFMovieOpen, FFGraphCreate, FFClockCreate, ...) take an explicit struct FastForwardBase *base argument because they are called both from the public LVO wrappers and from each other; they live in <fastforward/ff_internal.h> and are not part of the public ABI.
Public (LVO) Internal helper
OpenFFMovieA FFMovieOpen(base, tags)
CloseFFMovie FFMovieClose(base, m)
CreateFFGraphA FFGraphCreate(base, tags)
LinkFFPads FFPadLink(base, src, dst)
CreateFFClockA FFClockCreate(base, tags)

11. Extended Movie open tags

Beyond FFMT_Path, OpenFFMovieA() accepts:

Tag Purpose
FFMT_URL Network location (http/rtsp/hls). Until http.source lands, treated as path string for sniffing.
FFMT_MemData / FFMT_MemSize Play from memory via built-in mem.source.
FFMT_IOCallbacks / FFMT_IOUserData App-supplied read/seek/close (browser cache, RTSP shim).
FFMT_Timeout / FFMT_UserAgent / FFMT_ProxyServer Forwarded to future network source plugins.
FFMT_AsyncOpen / FFMT_OpenSignal Non-blocking open notification (reserved).

Read-only attrs via GetFFMovieAttrsA(): FFMT_HasAudio, FFMT_HasVideo, FFMT_HasSubtitle, FFMT_Seekable, FFMT_Width, FFMT_Height, FFMT_SampleRate, FFMT_Channels, FFMT_URL.

13. Pad and element introspection

Graph apps inspect negotiated formats before rewiring:

ULONG caps, linked;
struct FFAudioFormat *af;

GetFFPadAttrsA(pad,
    FFPA_Caps, (ULONG)&caps,
    FFPA_Linked, (ULONG)&linked,
    FFPA_AudioFormat, (ULONG)&af,
    TAG_DONE);

GetFFElementAttrsA(element,
    FFE_Kind, (ULONG)&kind,
    FFE_PadCount, (ULONG)&padcount,
    FFE_Caps, (ULONG)&caps,
    TAG_DONE);


3. Tape-deck transport

FastForward is named for the tape metaphor and the API leans into it: a movie is a tape, the engine is the deck, the application drives it with the same controls a real-world deck has.

PlayFFMovie(m);                  /* the Play button                       */
PauseFFMovie(m);                 /* the Pause button                      */
ResumeFFMovie(m);                /* alias of Play                         */
StopFFMovie(m);                  /* the Stop button                       */
RewindFFMovie(m);                /* Stop + seek to head ("rewind to 0")   */
EjectFFMovie(m);                 /* alias of CloseFFMovie                 */
SeekFFMovie(m, time, 0);         /* scrub the timeline                    */

FastForwardFFMovie(m);           /* play forward at 2x                    */
ReverseFFMovie(m);               /* play backward at 1x                   */
ScanFFMovie(m, FF_RATE_FF_4X);   /* arbitrary signed Q16.16 rate          */
ScanFFMovie(m, FF_RATE_REWIND_8X);
ScanFFMovie(m, FF_RATE_SLOW_HALF);

FFMT_PlayRate is the underlying tag — signed Q16.16 ticks per clock tick. 0x10000 is normal forward, -0x10000 is normal reverse, 0 is paused. The convenience macros expand to SetFFMovieAttrsA() calls so any plugin that honours FFMT_PlayRate participates automatically; codecs that only do 1x silently stay at 1x and the engine posts FFEV_QOS so the application can grey out the affordances that aren't supported.

Loop modes are exposed through FFMT_LoopMode:

Value Behaviour
FFLM_OFF Play once, stop at end. Default.
FFLM_LOOP Return to start at end.
FFLM_PINGPONG Alternate forward / reverse at boundaries.
FFLM_REGION Loop between FFMT_LoopStart and FFMT_LoopEnd.

Region mode is what a music sequencer drops onto a clip: set FFMT_LoopStart and FFMT_LoopEnd (in clock units), set FFMT_LoopMode = FFLM_REGION, and the engine seeks back to start whenever playback crosses end.


4. Track introspection and per-track control

Track ordering and metadata follow QuickTime's Movie/Track model: the demuxer enumerates independent substreams (audio, video, subtitle, MIDI, data) and exposes them as FFTrack handles.

ULONG count = GetFFTrackCount(m);
ULONG i;
for (i = 0; i < count; i++) {
    struct FFTrack *t = GetFFTrack(m, i);
    ULONG kind = 0;
    STRPTR desc = NULL, lang = NULL, codec = NULL;
    struct TagItem qtags[] = {
        { FFTA_Kind,        (ULONG)&kind  },
        { FFTA_Description, (ULONG)&desc  },
        { FFTA_Language,    (ULONG)&lang  },
        { FFTA_CodecName,   (ULONG)&codec },
        { TAG_DONE, 0 }
    };
    GetFFTrackAttrsA(t, qtags);
    /* kind == FFTK_AUDIO / FFTK_VIDEO / FFTK_DATA / FFTK_MIDI / FFTK_SUBTITLE */
}

Per-track mute / solo / volume / custom sink:

struct TagItem stags[] = {
    { FFTA_Mute,    TRUE         },
    { FFTA_Volume,  0x8000       },        /* 50% */
    { FFTA_Sink,    (ULONG)mySink },       /* FFElement * */
    { TAG_DONE, 0 }
};
SetFFTrackAttrsA(t, stags);

A "solo" UI button is implemented by muting every track except the selected one.


5. Graph API: editors, sequencers, transcoders

The Graph API is the low-level surface for code that wants to wire arbitrary elements together: non-linear editors, transcoders, music sequencers, live mixers, anything that needs more than the default linear source -> demux -> codec -> sink chain.

5.1 Building a graph by hand

struct FFGraph *g = CreateFFGraphA(NULL);

/* Add elements by name, identifier, kind, or explicit FFPlugin pointer. */
struct TagItem srcTags[] = {
    { FFE_Name, (ULONG)"FastForward file source" },
    { TAG_DONE, 0 }
};
struct FFElement *src = NewFFElementA(g, srcTags);

struct TagItem demuxTags[] = {
    { FFE_Kind, FFK_DEMUX },
    { FFE_Mime, (ULONG)"audio/wav" },
    { TAG_DONE, 0 }
};
struct FFElement *demux = NewFFElementA(g, demuxTags);

/* Wire pads. Each element exposes "src" (output) and/or "sink" (input). */
LinkFFPads(GetFFElementPad(src,   FFPAD_SRC),
           GetFFElementPad(demux, FFPAD_SINK));

Configure individual elements with SetFFElementAttrsA() (file path, AHI unit, volume, encoder bitrate, ...) and start the graph with RunFFGraph(). StopFFGraph() is safe to call from a signal handler or another task.

5.2 Auto-build a default pipeline

BuildFFGraphFromFileA() is a shortcut: returns a populated graph (source -> demux -> codec -> sink) for a path. Useful when you want the auto-detected chain but plan to swap the sink for a callback or transcoder element.

struct FFGraph *g = BuildFFGraphFromFileA("work:foo.wav", NULL);
struct FFElement *defaultSink = /* find the FFK_SINK element */;
DisposeFFElement(defaultSink);
struct FFElement *myMux = NewFFElementA(g, /* FFK_MUX tags */);
LinkFFPads(/* codec src */, GetFFElementPad(myMux, FFPAD_SINK));
RunFFGraph(g);

5.3 Graph layout for an editor or mixer

   track A: file_source -> demux -> codec -+
                                            +-> mixer -> ahi_sink
   track B: file_source -> demux -> codec -+

Mixer is itself an FFElement with two sink pads and one src pad. Editors free elements with DisposeFFElement() and rewire pads with UnlinkFFPad() / LinkFFPads(). The graph's master clock keeps every element timestamp-synchronised.

5.4 Disposal

DeleteFFGraph(g);

DeleteFFGraph() calls StopFFGraph() if needed, disposes every element it owns, and tears down all inter-element links.


6. Custom sinks: rendering inside your app

A non-linear editor wants every decoded video frame in a RastPort. A waveform widget wants every PCM block. A music sequencer wants samples available for offline mixing. Use the built-in callback sink.

LONG OnAudio(APTR user, APTR data, ULONG size,
             struct FFAudioFormat *af, ULONG ts) {
    /* hand 'data' (size bytes, format 'af', timestamp 'ts') to your widget */
    return 0;
}
LONG OnVideo(APTR user, APTR data, ULONG size,
             struct FFVideoFormat *vf, ULONG ts) {
    /* WritePixelArray() into your window's RastPort */
    return 0;
}

struct FFCallbacks cb;
cb.cb_OnFormat   = NULL;
cb.cb_OnAudio    = OnAudio;
cb.cb_OnVideo    = OnVideo;
cb.cb_OnSubtitle = NULL;
cb.cb_OnEvent    = NULL;
cb.cb_UserData   = my_widget;

struct FFElement *sink = NewFFCallbackSinkA(g, &cb, NULL);

Wire the callback sink as the audio or video sink of a Movie:

struct TagItem mtags[] = {
    { FFMT_Path,      (ULONG)path },
    { FFMT_AudioSink, (ULONG)sink },
    { TAG_DONE, 0 }
};
movie = OpenFFMovieA(mtags);

The callback runs on the player Process. Heavy work (Intuition, WritePixelArray into a deep mode, blocking DOS calls) should be deferred via a message port back to your main task.


7. Render targets and video output

Apps that want video drawn into one of their own Intuition surfaces hand it over via render-target hint tags on OpenFFMovieA() or SetFFMovieAttrsA(). FastForward routes the right pointer to whichever video sink ends up bound to the movie.

Tag Type Use
FFMT_Window struct Window * Render into the window's RastPort and re-use its IDCMP for resize events.
FFMT_Screen struct Screen * Render full-screen on a custom screen.
FFMT_RastPort struct RastPort * Explicit RastPort target.
FFMT_BitMap struct BitMap * Pre-allocated planar BitMap. Hand-rolled C2P sinks bind to this directly.
FFMT_DestX/Y LONG Destination top-left in the RastPort's coordinate space. Defaults to the window's inner top-left when FFMT_Window is set.
FFMT_DestWidth/Height ULONG Maximum destination rectangle. Zero means use the frame size.

The hints are advisory - if no video sink ends up plumbed for the stream (audio-only file, video-track disabled, ...) the hint is silently ignored.

For native AGA / OCS / ECS sinks the chunky-to-planar helper is documented in the Plugin Developer’s Guide.


8. Clock and synchronisation

Every Graph carries a master FFClock. Four backends:

Kind Backend Use
FFCK_TIMER timer.device UNIT_MICROHZ (ReadEClock) Default. ~1us resolution.
FFCK_REALTIME realtime.library Conductor Sequencers / NLE timelines that share tempo with CAMD, OctaMED, Bars&Pipes.
FFCK_AUDIO Slaves to the audio sink's sample clock A/V sync.
FFCK_EXTERNAL App calls SetFFClockTime() itself Mixer authoring tools.

Tags on CreateFFClockA():

Tag Use
FFCT_Kind FFCK_*
FFCT_TimeScale Ticks per second (default FF_TIMESCALE_USEC).
FFCT_Conductor realtime.library Conductor for FFCK_REALTIME.
FFCT_ConductorName Look up an existing named Conductor (CAMD, OctaMED).
FFCT_TickRate Tick rate hint (PPQN).
FFCT_Tempo BPM * 1000.
FFCT_ExternalSync TRUE: this clock's Player becomes the external sync source.
FFCT_PlayerHandle (struct Player **), output: receives the realtime.library Player.

8.1 Driving a sequencer manually

struct TagItem ctags[] = {
    { FFCT_Kind,      FFCK_REALTIME },
    { FFCT_TickRate,  192            },     /* PPQN */
    { FFCT_Tempo,     120000         },     /* 120.000 BPM */
    { TAG_DONE, 0 }
};
struct FFClock *clock = CreateFFClockA(ctags);
StartFFClock(clock);

while (running) {
    ULONG now = GetFFClockTime(clock);
    /* schedule next note at 'now + 1 tick' */
    WaitFFClock(clock, now + 1, SIGBREAKF_CTRL_C);
}

WaitFFClock() returns the bitmask of received signals, so Wait()-style multiplexing on Ctrl-C, IDCMP, MIDI input, et cetera works as usual.

8.2 External sync sources

realtime.library allows exactly one Player per Conductor to be the external sync source. That Player calls ExternalSync(player, minTime, maxTime) to constrain conductor time, and every other Player (including FastForward's) follows. On Amiga the natural sources are:

SMPTE timecode from a hardware genlock or video card (NLE deck control)
MIDI clock from external sequencer hardware via CAMD
AHI sample clock when audio is the timing master
LTC / wordclock from a pro audio interface card
Network sync (NTP, RTSP timestamps) for cross-machine playback
Two integration patterns are supported.

Pattern A — FastForward is the timing master. The application asks FastForward to become the external sync source. Our internal Player is created with PLAYER_ExtSync = TRUE, the Player handle is returned via FFCT_PlayerHandle, and the application calls ExternalSync() on it from its own time source:

struct Player *player = NULL;
struct TagItem ctags[] = {
    { FFCT_Kind,           FFCK_REALTIME              },
    { FFCT_ConductorName,  (ULONG)"FastForward.Conductor" },
    { FFCT_ExternalSync,   TRUE                       },
    { FFCT_PlayerHandle,   (ULONG)&player             }, /* out */
    { FFCT_TickRate,       192                        }, /* PPQN */
    { TAG_DONE, 0 }
};
struct FFClock *clock = CreateFFClockA(ctags);

/* In the audio interrupt or SMPTE reader: */
LONG hw_now = ReadHardwareTime();
ExternalSync(player, hw_now, hw_now);   /* clamp conductor to hw clock */

Pattern B — A third party owns the Conductor. Bars&Pipes, OctaMED, CAMD's metronome, or a custom genlock driver has already created a named Conductor and is calling ExternalSync() on its own Player. FastForward attaches to that Conductor by name and slaves to it automatically:

struct TagItem ctags[] = {
    { FFCT_Kind,          FFCK_REALTIME             },
    { FFCT_ConductorName, (ULONG)"BarsAndPipes"     }, /* the master */
    { TAG_DONE, 0 }
};
struct FFClock *clock = CreateFFClockA(ctags);
/* No FFCT_ExternalSync: we are a follower, not the master.       */
/* Every FFElement that uses FFE_SyncToConductor will now wake up */
/* in lock-step with whatever drives BarsAndPipes' conductor.     */

This is the same model every realtime.library client uses. CAMD's MIDI clock follows it, OctaMED's transport follows it, and now FastForward graphs do too.


9. Events

Plugins post FFEvent records (format change, seek complete, end-of-stream, decoded metadata tags, QoS feedback, structured errors) onto the owning Movie. The application drains them on its own task by waiting on the Movie's signal mask:

ULONG mask = GetFFMovieSignalMask(movie);
ULONG signals;
struct FFEvent ev;

signals = Wait(mask | mywindowmask | SIGBREAKF_CTRL_C);
while (PollFFMovieEvent(movie, &ev) > 0) {
    switch (ev.fe_Kind) {
    case FFEV_EOS:     /* loop or open next */                    break;
    case FFEV_FORMAT:  /* renegotiate; re-query track attrs */    break;
    case FFEV_SEEK:    /* seek completed */                       break;
    case FFEV_FLUSH:   /* upstream flushed */                     break;
    case FFEV_TAGS:    /* ev.fe_Tags: ID3 / Vorbis comment */     break;
    case FFEV_QOS:     /* dropped frames; widen ring buffers */   break;
    case FFEV_PROGRESS:/* buffering / transcode progress */       break;
    case FFEV_NETWORK: /* source went online / offline */         break;
    case FFEV_ERROR:   /* structured error report */              break;
    }
}

Plugins call host->fh_PostEvent(element, &event) to push events; the engine attributes them to the Movie that owns the graph.


10. Metadata

Movie metadata is queried with GetFFMovieAttrsA() using a request / response pair on FFMETA_Key plus FFMETA_String (for text) or FFMETA_Long (for numeric).

STRPTR artist = NULL;

GetFFMovieAttrsA(movie,
    FFMETA_Key,    FFMETA_Artist,
    FFMETA_String, (ULONG)&artist,
    TAG_DONE);

Keys: FFMETA_Title, FFMETA_Artist, FFMETA_Album, FFMETA_Genre, FFMETA_Comment, FFMETA_Copyright, FFMETA_Date, FFMETA_Language, FFMETA_CodecName, FFMETA_BitRate, FFMETA_Mime.

Demuxers post richer tag dumps via FFEV_TAGS events during playback; applications drain them through PollFFMovieEvent().

For canonical 4CC metadata interoperable with photo / catalogue tools, build the movie's mediatypes object instead and use the full metadataclass.h API. FastForward exposes the player-relevant subset; mediatypes owns the long tail (cover art, GPS, ID3 round-trip, sidecars, etc.).


11. Media probe (file managers, browsers)

Probe without building a playback graph:

struct FFMediaInfo *info;
STRPTR mime;
ULONG duration, tracks;

info = ProbeFFMediaA(
    FFPG_Path, "Work:Tunes/song.wav",
    FFPG_Recognition, FFREC_LIGHT,
    TAG_DONE);

GetFFMediaInfoAttrsA(info,
    FFPG_Mime,       (ULONG)&mime,
    FFPG_Duration,   (ULONG)&duration,
    FFPG_TrackCount, (ULONG)&tracks,
    TAG_DONE);

FreeFFMediaInfo(info);

Sources accepted by ProbeFFMediaA():

Tag Use
FFPG_Path Local file
FFPG_FileHandle Already-open BPTR
FFPG_MemData + FFPG_MemSize Buffer already in RAM
FFPG_URL Extension / MIME guess before a network source connects

FFREC_LIGHT sniffs headers only. FFREC_HEAVY opens the demuxer briefly to learn duration when p_QueryDuration exists.

Probe attributes available via GetFFMediaInfoAttrsA():

FFPG_Mime, FFPG_Score (0..100), FFPG_Duration, FFPG_TrackCount, FFPG_HasAudio, FFPG_HasVideo, FFPG_HasSubtitle, FFPG_Seekable, FFPG_Width, FFPG_Height, FFPG_SampleRate, FFPG_Channels, FFPG_Title, FFPG_DemuxerName.


12. Thumbnails and still images

DecodeFFThumbnailA() decodes one video frame when a video codec plugin is available:

struct FFFrameResult *frame = DecodeFFThumbnailA(
    FFTH_Path,        path,
    FFTH_Time,        durationUsec / 2,    /* halfway through */
    FFTH_Width,       320,
    FFTH_Height,      240,
    FFTH_PixelFormat, FFVF_RGB24,
    TAG_DONE);

if (frame != NULL) {
    /* frame->fr_Data is fr_Width x fr_Height pixels, stride fr_Stride */
    WritePixelArray(frame->fr_Data, 0, 0, frame->fr_Stride,
                    rp, x, y, frame->fr_Width, frame->fr_Height,
                    RECTFMT_RGB);
    FreeFFFrameResult(frame);
}

Still bitmaps (PNG, ILBM, JPEG, GIF) should use datatypes.library (ReadDTObjectA) for Workbench and file-manager previews until png.codec / ilbm.codec plugins land. DecodeFFThumbnailA() returns NULL with FFERR_NOTSUPPORTED and a message directing apps to datatypes for those MIME types.

Animated GIFs are timed media: future gif.demux exposes them as a video track with frame delays in FFImageFormat. Until then, gifanim.datatype covers icon/preview use.

Subtitle files (.srt, .ssa) are FFTK_SUBTITLE tracks decoded by srt.codec / ssa.codec plugins producing FFBT_SUBTITLE buffers with FFSubtitleFormat timing. Plain .txt viewing remains a datatypes concern.


13. Errors and cancellation

struct FFErrorInfo err;

if (OpenFFMovieA(...) == NULL) {
    GetFFLastError(&err);
    /* err.ei_Code, err.ei_Message, err.ei_Component */
}

CancelFFMovie(movie);   /* abort streaming / hung open */

GetFFLastError() returns a per-task structured error record - same model as IoErr() on dos.library. The component / function names in the record name the originating engine subsystem or plugin so the application can include them in error dialogues without having to maintain its own translation table.

Error codes:

Code Meaning
FFERR_NONE (0) Success
FFERR_NOMEM (-1) Out of memory
FFERR_NODEV (-2) Device unavailable
FFERR_FORMAT (-3) Unrecognised container
FFERR_CODEC (-4) Codec failure
FFERR_STREAM (-5) Stream error
FFERR_SYNC (-6) Synchronisation failure
FFERR_BUFFER (-7) Buffer underrun / overflow
FFERR_NOTOPEN (-8) Object not open
FFERR_NOWINDOW (-9) Render target missing
FFERR_HARDWARE (-10) Hardware error
FFERR_DECODE (-11) Decoder rejected data
FFERR_NETWORK (-12) Network failure
FFERR_TIMEOUT (-13) Operation timed out
FFERR_NOTSEEKABLE (-14) Source cannot seek
FFERR_CANCELLED (-15) Cancelled by caller
FFERR_NOTFOUND (-16) Resource not found
FFERR_NOTSUPPORTED (-17) Feature unsupported


14. Per-element tasks and Conductor synchronisation

FastForward maps the BeOS Media Kit's node-per-thread topology onto Exec primitives. Every FFElement defaults to FFER_INLINE so existing linear graphs keep working unchanged; heavy elements opt into a dedicated Task by passing tags at creation time:

struct TagItem etags[] = {
    { FFE_Plugin,           (ULONG)plugin },
    { FFE_RunMode,          FFER_TASK     },  /* Exec Task, no DOS calls */
    { FFE_TaskPriority,     5             },  /* nice and quick */
    { FFE_StackSize,        16384         },
    { FFE_SyncToConductor,  TRUE          },  /* slave to graph's master clock */
    { FFE_PoolDepth,        4             },  /* 4 buffers in flight per link */
    { TAG_DONE, 0 }
};
struct FFElement *e = NewFFElementA(graph, etags);

Tag Meaning
FFE_RunMode = FFER_INLINE Run on caller's task. Default. Zero overhead, single-thread linear graphs.
FFE_RunMode = FFER_TASK Dedicated Exec Task. Use for codecs, filters, mixers - anything CPU-bound that should not stall its neighbours.
FFE_RunMode = FFER_PROCESS Dedicated DOS Process via CreateNewProc. Use for sources and sinks that call Lock, Read, OpenDevice etc.
FFE_TaskPriority BYTE priority (-128..+127). Default 0. Audio sinks usually +5; demuxers 0; speculative decoders -5.
FFE_StackSize Bytes; default 8192. Increase for stack-heavy codecs.
FFE_SyncToConductor BOOL. When set, FastForward attaches a realtime.library Player to the graph's master Conductor and signals the element's task on PLAYER_AlarmSigBit at frame boundaries. The BeOS BTimeSource pattern done with the Amiga primitive that already exists. Use it on producers (sources, decoders) so they wake at the right time instead of as fast as they can push.
FFE_PoolDepth Number of buffers in the per-link ring. 2 is the minimum (double-buffer); 4 is the default; deeper rings absorb more jitter at the cost of more RAM.

When the graph runs, RunFFGraph() walks the element list, starts a Task for every element whose FFE_RunMode is non-INLINE, allocates per-element message ports and per-link buffer pools, and - for any element that asked for FFE_SyncToConductor - creates a Player attached to the graph's Conductor. StopFFGraph() signals every running task to exit and tears the plumbing down.

14.1 When to use task mode versus inline

Use case Recommended layout
Simple WAV / AIFF player All FFER_INLINE. The MVP audio path is single-thread by design — task overhead would be pure waste.
MP3 + AHI on a 68040 Codec FFER_TASK, AHI sink FFER_PROCESS. Decoder runs ahead of the sink; sink pulls from the buffer pool when DMA needs more samples.
NLE timeline Each track as its own FFER_TASK, all attached to the same Conductor with FFE_SyncToConductor. Track heads stay sample-accurate even when one track's decoder briefly stalls.
Music sequencer Producers (synth, sampler) FFER_TASK with FFE_SyncToConductor, mixer FFER_TASK, AHI sink FFER_PROCESS. The Conductor is shared with OctaMED / CAMD so external transport drives the lot.
Network stream Source FFER_PROCESS (DOS-side socket I/O), demux FFER_TASK (parse ahead, absorb jitter via deeper FFE_PoolDepth), codec FFER_TASK, sink FFER_PROCESS.

14.2 Topology granularity rule of thumb

Exec context switching is fast on classic Amiga hardware but it is not free. Every FFER_TASK / FFER_PROCESS boundary costs roughly one Signal and one Wait per buffer plus the cache disturbance of a context flush. On a stock 68030 a five-stage fine-grained pipeline (source → demux → filter A → filter B → codec → sink, each on its own task) can spend more cycles in Signal / WaitPort than in actual decode work; the asynchronicity win is overwhelmed by the per-frame switching cost.

Recommended pattern on classic 68k:

Default everything to FFER_INLINE. The graph builder honours
that; a single caller-task slice runs Pull → Decode → Filter sequentially with no port traffic at all.

• **Cross task boundaries only where you actually want the
asynchrony**: disk / network I/O on the source, the AHI / video sink (so it can run at high priority), or one CPU-heavy codec that wants to pace ahead of its consumer.

Treat fine-grained per-filter tasks as a 68060 / RTG luxury.
They are the right answer when the cost of a context switch is dwarfed by the cost of the work between switches; they are the wrong answer when the work between switches is itself short.

• The AHI sink is the canonical example of where the boundary
pays off: it is FFER_PROCESS (because OpenDevice wants DOS context), it bumps its own task priority once playback starts so an Intuition window-drag cannot starve it, and it pulls from the pool whenever AHI's mixer hook fires. Everything upstream can stay FFER_INLINE if the codec is light enough.

The framework gives you the asynchronous primitives; the graph builder does not silently fold elements together to "save context switches". Pick the layout that suits your hardware and your workload, and audit it on the slowest CPU you intend to support.


15. player.gadget and playback.gadget

player.gadget and playback.gadget are BOOPSI gadget classes for embedding FastForward playback inside Intuition windows. They wrap the same Movie API that Play uses, so application authors get a viewport and tape-deck transport without building custom UI or managing the player process directly.

15.1 Roles

Gadget Responsibility
player.gadget Hosts the movie viewport, opens the FFMovie, and renders decoded video into its gadget box (or forwards audio to AHI). Handles resize and refresh.
playback.gadget Supplies tape-deck controls (play, pause, stop, seek, scan) that drive the active movie attached to a player.gadget.

15.2 Install layout

Gadget class descriptors ship with FastForward alongside the library and plugins:

LIBS:fastforward.library
Classes/Gadgets/player.gadget
Classes/Gadgets/playback.gadget

Link your application against fastforward.library and open the gadget classes with OpenLibrary() the same way you would for any BOOPSI gadget. The gadgets themselves call into the Movie API internally.

15.3 Typical window layout

Place a player.gadget in the main content area and a playback.gadget along the bottom edge. The playback gadget must reference the player gadget that owns the movie:

struct Gadget *player_gad, *transport_gad;

player_gad = (struct Gadget *)NewObject(NULL, "player.gadget",
    GA_Left,        10,
    GA_Top,         10,
    GA_Width,       win->Width - 20,
    GA_Height,      win->Height - 60,
    GA_RelVerify,   TRUE,
    PLAYERGAD_Path, (ULONG)path,
    TAG_END);

transport_gad = (struct Gadget *)NewObject(NULL, "playback.gadget",
    GA_Left,        10,
    GA_Top,         win->Height - 44,
    GA_Width,       win->Width - 20,
    GA_Height,      32,
    PLAYBACKGAD_Player, (ULONG)player_gad,
    TAG_END);

PLAYERGAD_Path is the primary open tag (local file path today; URL tags follow the same Movie API rules as FFMT_URL). PLAYBACKGAD_Player wires transport buttons to the movie owned by that player gadget instance.

15.4 Common player.gadget tags

Tag Type Purpose
PLAYERGAD_Path STRPTR Media file to open when the gadget is instantiated.
PLAYERGAD_URL STRPTR Network source (when a streaming source plugin is available).
PLAYERGAD_AutoPlay BOOL Start playback immediately after open (mirrors FFMT_AutoPlay).
PLAYERGAD_Loop BOOL Loop at end-of-stream (mirrors FFMT_Loop).
PLAYERGAD_Movie struct FFMovie * Attach an application-created movie instead of opening by path. The gadget renders into its box but does not own the CloseFFMovie() call unless you set PLAYERGAD_OwnMovie.

15.5 Event loop

The gadget owns the underlying FFMovie process. Your application still waits on the window IDCMP port for gadget input, and may also merge the movie signal mask when you need end-of-stream or error notifications at the application level:

ULONG sigs, mask = (1UL << win->UserPort->mp_SigBit);

/* optional: OR in GetFFMovieSignalMask(movie) when you opened the movie yourself */

while (running) {
    sigs = Wait(mask);
    if (sigs & (1UL << win->UserPort->mp_SigBit)) {
        struct IntuiMessage *imsg;
        while ((imsg = (struct IntuiMessage *)GT_GetIMsg(win->UserPort))) {
            switch (imsg->Class) {
            case IDCMP_GADGETUP:
                /* layout gadgets handle playback; app handles the rest */
                break;
            case IDCMP_CLOSEWINDOW:
                running = FALSE;
                break;
            }
            GT_ReplyIMsg(imsg);
        }
    }
}

15.6 Gadgets vs raw Movie API

Use the gadgets when you want a standard player UI inside an Intuition window. Use the raw Movie API (sections 2–7) when you need a custom render path (FFMT_VideoSink callback), per-track control in a non-standard layout, or graph-based editing. You can combine both: open a movie yourself, pass it with PLAYERGAD_Movie, and keep using SetFFTrackAttrsA() on the underlying handle retrieved via GetAttr(PLAYERGAD_Movie, player_obj, ...).


16. mediatypes.library integration

mediatypes.library owns media-type identification (MIME / format kind) and the BOOPSI object surface. FastForward owns the multimedia pipeline runtime. The two libraries are designed to be used together:

• For format / MIME identification, applications hand bytes to
SniffMediaTypeA() / ExamineMT() (mediatypes) rather than reinventing detection in FastForward.

• For full multimedia playback / transcoding / mixing, a mediatypes
object identifies the format and exposes substreams via containerclass.h; the object then builds a FastForward Graph and hands execution off — fastforward.library provides typed FIFO endpoints, demuxers, decoders, renderers, sinks.

• Conversely, FastForward defers to mediatypes for format
recognition. Until mediatypes.library ships, the engine falls back to a small built-in sniffer.

In practice a player application can use either surface:

• Use FastForward directly (OpenFFMovieA) when you only need to
play a single file, you don't care about generic BOOPSI dispatch, and you don't need cover-art / sidecar / Workbench integration.

• Use mediatypes (NewMTObjectA) when you want a uniform handle to
a piece of media, with full metadata, sidecar persistence, catalogue-tool support, optional datatypes.library interoperability, and an optional hand-off to FastForward when real playback is requested.


17. Reference: enums, error codes, transport rates

17.1 Movie state (GetFFMovieState)

State Value Meaning
FFMS_STOPPED 0 Opened but not yet started
FFMS_OPENED 1 Synchronous open completed
FFMS_PREROLL 2 Filling buffers before play
FFMS_PLAYING 3 Actively decoding and presenting
FFMS_PAUSED 4 Decoding suspended at position
FFMS_FINISHED 5 Reached EOS without error
FFMS_ERROR 6 Playback aborted; see GetFFLastError()

17.2 Track kinds (FFTA_Kind)

Kind Value
FFTK_AUDIO 1
FFTK_VIDEO 2
FFTK_DATA 3
FFTK_MIDI 4
FFTK_SUBTITLE 5

17.3 Source kinds (FFSO_Kind)

Kind Value Use
FFSK_FILE 1 DOS file
FFSK_URL 2 Network location
FFSK_MEMORY 3 In-memory buffer
FFSK_CALLBACK 4 FFIOCallbacks byte stream

17.4 Clock kinds (FFCT_Kind)

Kind Value Backend
FFCK_TIMER 1 timer.device EUNIT_MICROHZ
FFCK_REALTIME 2 realtime.library Conductor
FFCK_AUDIO 3 Audio sink sample clock
FFCK_EXTERNAL 4 App-driven

17.5 Seek / control flags (SeekFFMovie)

Flag Meaning
FFCF_FLUSH Discard pending buffers before seeking
FFCF_KEYFRAME Snap to the nearest keyframe
FFCF_BACKWARD Prefer seeking backwards over forwards
FFCF_ACCURATE Decode forward from keyframe to land on the exact frame

17.6 Transport rate presets (FFMT_PlayRate, signed Q16.16)

Constant Hex Meaning
FF_RATE_STOP 0x00000000 Stop / pause
FF_RATE_PAUSE 0x00000000 Alias for STOP
FF_RATE_PLAY 0x00010000 Normal forward (1.0)
FF_RATE_FF_2X 0x00020000 Fast-forward 2x
FF_RATE_FF_4X 0x00040000 Fast-forward 4x
FF_RATE_FF_8X 0x00080000 Fast-forward 8x
FF_RATE_SLOW_HALF 0x00008000 Slow motion (0.5)
FF_RATE_REVERSE -0x00010000 Normal reverse
FF_RATE_REWIND_2X -0x00020000 Rewind 2x
FF_RATE_REWIND_4X -0x00040000 Rewind 4x
FF_RATE_REWIND_8X -0x00080000 Rewind 8x

17.7 Loop modes (FFMT_LoopMode)

Mode Value Behaviour
FFLM_OFF 0 Play once, stop at end
FFLM_LOOP 1 Return to start at end
FFLM_PINGPONG 2 Alternate forward / reverse at boundaries
FFLM_REGION 3 Loop between FFMT_LoopStart and FFMT_LoopEnd

17.8 Event kinds (FFEvent.fe_Kind)

Kind Value Carried payload
FFEV_FORMAT 1 Format change negotiation
FFEV_SEEK 2 Downstream seek completed
FFEV_FLUSH 3 Upstream flushed pending data
FFEV_EOS 4 End of stream
FFEV_TAGS 5 Metadata tag list available
FFEV_QOS 6 Quality-of-service feedback
FFEV_PROGRESS 7 Buffering / transcode progress
FFEV_NETWORK 8 Network state change
FFEV_ERROR 9 Structured error posted to movie

17.9 Probe recognition (FFPG_Recognition)

Mode Value Behaviour
FFREC_LIGHT 0 Header sniff only; fast
FFREC_HEAVY 1 Open demuxer briefly for duration / tracks

17.10 Buffer types (FFBuffer.fb_Type)

Type Value Use
FFBT_UNKNOWN 0 Untyped / placeholder
FFBT_PACKET 1 Opaque container packet
FFBT_PCM 2 Decoded PCM audio
FFBT_VIDEO 3 Decoded video frame
FFBT_DATA 4 Generic data
FFBT_EVENT 5 Control event, no payload
FFBT_SUBTITLE 6 Timed text cue (see FFSubtitleFormat)
FFBT_IMAGE 7 Still or animated frame (see FFImageFormat)
FFBT_TEXT 8 Plain untimed text chunk

17.11 Log levels (SetFFLogLevel)

Level Value Behaviour
FFLOG_NONE 0 Silence
FFLOG_ERROR 1 Errors only
FFLOG_WARN 2 Warnings and errors (default)
FFLOG_INFO 3 Informational notices
FFLOG_DEBUG 4 Verbose internal traces


Appendix A: Quick recipes

A.1 Play an audio file in 30 lines of C

#include <libraries/fastforward.h>
#include <proto/fastforward.h>
#include <proto/exec.h>
#include <dos/dos.h>

struct Library *FastForwardBase;

int main(int argc, char **argv)
{
    struct FFMovie *m;
    ULONG mask, signals;
    struct FFEvent ev;

    if (argc < 2) return 5;

    FastForwardBase = FF_OpenLibrary(FF_API_VERSION);
    if (!FastForwardBase) return 20;
    LoadFFPlugins(NULL);

    m = OpenFFMovie(FFMT_Path,     (ULONG)argv[1],
                    FFMT_AutoPlay, TRUE);
    if (!m) goto out;

    mask = GetFFMovieSignalMask(m);
    while (GetFFMovieState(m) == FFMS_PLAYING ||
           GetFFMovieState(m) == FFMS_PREROLL) {
        signals = Wait(mask | SIGBREAKF_CTRL_C);
        if (signals & SIGBREAKF_CTRL_C) { StopFFMovie(m); break; }
        while (PollFFMovieEvent(m, &ev) > 0)
            if (ev.fe_Kind == FFEV_EOS) goto done;
    }
done:
    CloseFFMovie(m);
out:
    UnloadFFPlugins();
    FF_CloseLibrary(FastForwardBase);
    return 0;
}

A.2 Background music for a game (looped)

movie = OpenFFMovie(
    FFMT_Path,     (ULONG)"music:level1.mod",
    FFMT_AutoPlay, TRUE,
    FFMT_Loop,     TRUE,
    FFMT_Volume,   0x8000);   /* 50% so SFX bury it cleanly */

A.3 Show a poster frame for a video file

struct FFFrameResult *frame = DecodeFFThumbnail(
    FFTH_Path,        clipPath,
    FFTH_Time,        FF_TIMESCALE_USEC * 5,  /* 5 seconds in */
    FFTH_Width,       160,
    FFTH_Height,      120,
    FFTH_PixelFormat, FFVF_RGB24);
if (frame) {
    WritePixelArray(frame->fr_Data, 0, 0, frame->fr_Stride,
                    rp, x, y, frame->fr_Width, frame->fr_Height,
                    RECTFMT_RGB);
    FreeFFFrameResult(frame);
}

A.4 Probe a folder and populate a file-manager listing

struct ExAllControl *ctrl = AllocDosObject(DOS_EXALLCONTROL, NULL);
/* ... ExAll loop omitted ... */
for (each entry e) {
    struct FFMediaInfo *info = ProbeFFMedia(
        FFPG_Path,        e->ed_Name,
        FFPG_Recognition, FFREC_LIGHT);
    if (info) {
        STRPTR mime = NULL;
        ULONG  dur  = 0;
        GetFFMediaInfoAttrs(info,
            FFPG_Mime,     (ULONG)&mime,
            FFPG_Duration, (ULONG)&dur);
        AddRow(e->ed_Name, mime, dur);
        FreeFFMediaInfo(info);
    }
}

A.5 Cancel a stuck network open

movie = OpenFFMovie(FFMT_URL, (ULONG)"http://example.com/foo.mp4",
                    FFMT_AsyncOpen, TRUE,
                    FFMT_OpenSignal, openSig);
/* user clicks Cancel before the OpenSignal fires */
CancelFFMovie(movie);
CloseFFMovie(movie);

A.6 Render decoded audio into your own widget

LONG OnAudio(APTR user, APTR data, ULONG size,
             struct FFAudioFormat *af, ULONG ts) {
    Waveform_Append((Waveform *)user, data, size, af->af_SampleRate);
    return 0;
}

struct FFCallbacks cb = { NULL, OnAudio, NULL, NULL, NULL, my_widget };
struct FFGraph    *g  = BuildFFGraphFromFile(path, TAG_DONE);
struct FFElement  *sink = NewFFCallbackSink(g, &cb, TAG_DONE);
/* find and replace the default audio sink with `sink`, then run */
RunFFGraph(g);

A.7 Transcode a file in five lines

struct FFGraph *g = BuildFFGraphFromFile("in.wav", TAG_DONE);
/* find the default sink and dispose it; install a file mux instead */
ReplaceWithFileMux(g, "out.aiff");
RunFFGraph(g);
WaitForGraphCompletion(g);
DeleteFFGraph(g);

A.8 Tempo-quantised sequencer step

struct FFClock *clk = CreateFFClock(
    FFCT_Kind,     FFCK_REALTIME,
    FFCT_TickRate, 192,                 /* PPQN */
    FFCT_Tempo,    140000);             /* 140 BPM */
StartFFClock(clk);

while (running) {
    ULONG now  = GetFFClockTime(clk);
    ULONG sigs = WaitFFClock(clk, now + 1, midiInSig | SIGBREAKF_CTRL_C);
    if (sigs & SIGBREAKF_CTRL_C) break;
    if (sigs & midiInSig) Drain_MidiInput();
    Trigger_NextStep(now + 1);
}


Appendix B: Integration cheatsheet

Goal Recipe
Play a file Open Movie with FFMT_Path and FFMT_AutoPlay. Wait on signal mask.
Background music for a game Same, plus FFMT_Loop.
Custom video output NewFFCallbackSinkA(), pass via FFMT_VideoSink.
Waveform display FFMT_AudioSink = callback sink. Hand samples to a widget.
Transcoder BuildFFGraphFromFileA(), replace sink with a file mux element, run graph.
NLE timeline CreateFFGraphA(), multiple sources via mixer / compositor element, master FFClock FFCK_REALTIME.
Sequencer CreateFFClockA(FFCK_REALTIME), dispatch events with WaitFFClock().
File manager preview ProbeFFMediaA() for metadata; still images via datatypes.library; timed media via DecodeFFThumbnailA() when a video decoder exists.
Browser / streaming OpenFFMovieA(FFMT_URL, url, ...) today; http.source / hls.demux plugins supply bytes later.
Cancel network open CancelFFMovie() signals the player process and posts FFEV_ERROR.
Tape-deck rewind RewindFFMovie(m) (composes StopFFMovie + SeekFFMovie(m, 0, 0)).
Fast-forward 4x ScanFFMovie(m, FF_RATE_FF_4X).
Embed player in a window player.gadget + playback.gadget (section 15).


Appendix A: Quick recipes

A.1 Play an audio file in 30 lines of C

#include <libraries/fastforward.h>
#include <proto/fastforward.h>
#include <proto/exec.h>
#include <dos/dos.h>

struct Library *FastForwardBase;

int main(int argc, char **argv)
{
    struct FFMovie *m;
    ULONG mask, signals;
    struct FFEvent ev;

    if (argc < 2) return 5;

    FastForwardBase = FF_OpenLibrary(FF_API_VERSION);
    if (!FastForwardBase) return 20;
    LoadFFPlugins(NULL);

    m = OpenFFMovie(FFMT_Path,     (ULONG)argv[1],
                    FFMT_AutoPlay, TRUE);
    if (!m) goto out;

    mask = GetFFMovieSignalMask(m);
    while (GetFFMovieState(m) == FFMS_PLAYING ||
           GetFFMovieState(m) == FFMS_PREROLL) {
        signals = Wait(mask | SIGBREAKF_CTRL_C);
        if (signals & SIGBREAKF_CTRL_C) { StopFFMovie(m); break; }
        while (PollFFMovieEvent(m, &ev) > 0)
            if (ev.fe_Kind == FFEV_EOS) goto done;
    }
done:
    CloseFFMovie(m);
out:
    UnloadFFPlugins();
    FF_CloseLibrary(FastForwardBase);
    return 0;
}

A.2 Background music for a game (looped)

movie = OpenFFMovie(
    FFMT_Path,     (ULONG)"music:level1.mod",
    FFMT_AutoPlay, TRUE,
    FFMT_Loop,     TRUE,
    FFMT_Volume,   0x8000);   /* 50% so SFX bury it cleanly */

A.3 Show a poster frame for a video file

struct FFFrameResult *frame = DecodeFFThumbnail(
    FFTH_Path,        clipPath,
    FFTH_Time,        FF_TIMESCALE_USEC * 5,  /* 5 seconds in */
    FFTH_Width,       160,
    FFTH_Height,      120,
    FFTH_PixelFormat, FFVF_RGB24);
if (frame) {
    WritePixelArray(frame->fr_Data, 0, 0, frame->fr_Stride,
                    rp, x, y, frame->fr_Width, frame->fr_Height,
                    RECTFMT_RGB);
    FreeFFFrameResult(frame);
}

A.4 Probe a folder and populate a file-manager listing

struct ExAllControl *ctrl = AllocDosObject(DOS_EXALLCONTROL, NULL);
/* ... ExAll loop omitted ... */
for (each entry e) {
    struct FFMediaInfo *info = ProbeFFMedia(
        FFPG_Path,        e->ed_Name,
        FFPG_Recognition, FFREC_LIGHT);
    if (info) {
        STRPTR mime = NULL;
        ULONG  dur  = 0;
        GetFFMediaInfoAttrs(info,
            FFPG_Mime,     (ULONG)&mime,
            FFPG_Duration, (ULONG)&dur);
        AddRow(e->ed_Name, mime, dur);
        FreeFFMediaInfo(info);
    }
}

A.5 Cancel a stuck network open

movie = OpenFFMovie(FFMT_URL, (ULONG)"http://example.com/foo.mp4",
                    FFMT_AsyncOpen, TRUE,
                    FFMT_OpenSignal, openSig);
/* user clicks Cancel before the OpenSignal fires */
CancelFFMovie(movie);
CloseFFMovie(movie);

A.6 Render decoded audio into your own widget

LONG OnAudio(APTR user, APTR data, ULONG size,
             struct FFAudioFormat *af, ULONG ts) {
    Waveform_Append((Waveform *)user, data, size, af->af_SampleRate);
    return 0;
}

struct FFCallbacks cb = { NULL, OnAudio, NULL, NULL, NULL, my_widget };
struct FFGraph    *g  = BuildFFGraphFromFile(path, TAG_DONE);
struct FFElement  *sink = NewFFCallbackSink(g, &cb, TAG_DONE);
/* find and replace the default audio sink with `sink`, then run */
RunFFGraph(g);

A.7 Transcode a file in five lines

struct FFGraph *g = BuildFFGraphFromFile("in.wav", TAG_DONE);
/* find the default sink and dispose it; install a file mux instead */
ReplaceWithFileMux(g, "out.aiff");
RunFFGraph(g);
WaitForGraphCompletion(g);
DeleteFFGraph(g);

A.8 Tempo-quantised sequencer step

struct FFClock *clk = CreateFFClock(
    FFCT_Kind,     FFCK_REALTIME,
    FFCT_TickRate, 192,                 /* PPQN */
    FFCT_Tempo,    140000);             /* 140 BPM */
StartFFClock(clk);

while (running) {
    ULONG now  = GetFFClockTime(clk);
    ULONG sigs = WaitFFClock(clk, now + 1, midiInSig | SIGBREAKF_CTRL_C);
    if (sigs & SIGBREAKF_CTRL_C) break;
    if (sigs & midiInSig) Drain_MidiInput();
    Trigger_NextStep(now + 1);
}


Appendix B: Integration cheatsheet

Goal Recipe
Play a file Open Movie with FFMT_Path and FFMT_AutoPlay. Wait on signal mask.
Background music for a game Same, plus FFMT_Loop.
Custom video output NewFFCallbackSinkA(), pass via FFMT_VideoSink.
Waveform display FFMT_AudioSink = callback sink. Hand samples to a widget.
Transcoder BuildFFGraphFromFileA(), replace sink with a file mux element, run graph.
NLE timeline CreateFFGraphA(), multiple sources via mixer / compositor element, master FFClock FFCK_REALTIME.
Sequencer CreateFFClockA(FFCK_REALTIME), dispatch events with WaitFFClock().
File manager preview ProbeFFMediaA() for metadata; still images via datatypes.library; timed media via DecodeFFThumbnailA() when a video decoder exists.
Browser / streaming OpenFFMovieA(FFMT_URL, url, ...) today; http.source / hls.demux plugins supply bytes later.
Cancel network open CancelFFMovie() signals the player process and posts FFEV_ERROR.
Tape-deck rewind RewindFFMovie(m) (composes StopFFMovie + SeekFFMovie(m, 0, 0)).
Fast-forward 4x ScanFFMovie(m, FF_RATE_FF_4X).
Embed player in a window player.gadget + playback.gadget (section 15).


Return to the FastForward Overview.  |  Back to top