FastForward User’s Guide
Install, play media, inspect files, and configure plugins
Installation, playback with Play, media inspection with Examine, and day-to-day use of FastForward on AmigaOS 3.
FastForward tools
FastForward comprises:
fastforward.library — the shared engine: plugin registry, pipeline and filter graph, movie and graph APIs, sniffing, buffer pools, clocks, events, probe, stats, metadata and thumbnail support
Play — Universal media player tool (Source/play/)
Examine — Shell command media inspector; reports containers, streams, and which plugins would bind to the file being examined
player.gadget and playback.gadget — BOOPSI gadget classes for embedding a media player and its playback controls in Intuition applications
Built-in plugins
mem.source — in-memory byte streams
callback.sink — delivers frames to application callbacks
FastForward plugins
Sources — file.source for local files, http-live.source for HTTP-LS streams, rtsp.source for RTSP streams
Demuxers — pcm.demux, mov.demux, avi.demux, cdxl.demux, au.demux, fpadpcm.demux, rm.demux, mpegaudio.demux, mpeg.demux, ts.demux, ogg.demux, flac.demux, mkv.demux
Codecs — pcm.codec, ima4.codec, fpadpcm.codec, msadpcm.codec, dviadpcm.codec, mace.codec, mpegaudio.codec, mpegvideo.codec, vorbis.codec, cvid.codec, svq1.codec, msvc.codec, rpza.codec, smc.codec, qtrle.codec, cdxl.codec, ra144.codec, ra288.codec, a52.codec, aac.codec
Filters — volume.filter, fade.filter
Sinks — audio.sink, ahi.sink, video.sink, ham.sink, null.sink
Embedding playback: player.gadget and playback.gadget
player.gadget and playback.gadget are BOOPSI gadget classes for embedding a media player and its transport controls inside Intuition applications.
player.gadget — hosts the movie viewport and ties into fastforward.library
for decode and render.
playback.gadget — supplies tape-deck controls (play, pause, stop, seek)
that drive the active movie.
Both gadgets link against fastforward.library the same way Play does. Application authors drop them into a window layout instead of building custom transport UI. See the Developer’s Guide (section 15) for gadget tags, Movie API integration, and custom sink options.
Inspecting media with Examine
Examine is a Shell command media inspector (Source/examine/). It reports container format, stream layout, duration, and which FastForward plugins would bind to the file — without starting playback.
From a Shell prompt:
Examine Work:Media/song.wav
Examine Work:Video/clip.mov |
Examine uses the same sniff and probe path as ProbeFFMediaA() in fastforward.library: the engine inspects the byte stream, scores registered plugins, and prints the winning source, demuxer, codec, and sink chain.
Use Examine when a file fails to play, when you want to confirm which plugin handles a format, or when building a file manager that needs format metadata before opening a movie.
Install layout
LIBS:fastforward.library
FastForward: (ASSIGN, packagers' choice of physical path)
FastForward:Plugins/file.source
FastForward:Plugins/pcm.demux
FastForward:Plugins/pcm.codec
FastForward:Plugins/ahi.sink
C:Play |
The plugin directory is resolved dynamically at init time, not compiled in. FFLoaderResolveDir() picks, in order:
1. ENV: FastForward/Plugins
2. logical assign FastForward: (then Plugins/ beneath it)
3. fallback LIBS:FastForward/Plugins
Packagers add ASSIGN FastForward: SYS:Classes/FastForward (or wherever they prefer) to User-Startup, and any plugin dropped into that directory is picked up on the next library open. This mirrors the way datatypes.library discovers descriptors via a known assign, and is the same hook the sibling mediatypes.library design uses for its registry.
All plugin binaries install into the same Plugins/ directory. Optional plugins can be added or removed without rebuilding the core library.
Plugin directory resolution
When an application (including Play) opens fastforward.library, plugins are loaded from the first location that exists:
LoadFFPlugins(NULL) uses the default directory, which is resolved 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 have to know. Passing an explicit path to LoadFFPlugins() overrides the default and lets transcoders and editors load from a private bundle. Built-in plugins (file source, PCM demux, PCM codec, AHI sink, callback sink, memory source) are registered automatically.
On-target verification
Prerequisites
AmigaOS 3.x 68k (020–060)
AHI 6.x installed (ahi.device v4+ API)
fastforward.library and four plugin files installed (see Docs/ARCHITECTURE.md)
Play in C: or project directory
Test files: WAV (PCM), AIFF, IFF 8SVX, IFF 16SV
Build (on Amiga)
From FastForward/:
Copy fastforward.library to LIBS:, plugins to LIBS:FastForward/Plugins/, Play to C:.
Playback test
Play test.wav
Play test.aiff
Play test.8svx
Play test.16sv |
Expect audible output through the default AHI mode. Ctrl+C aborts during playback.
Workbench: drag a file onto Play.
Memory check
Before and after playback, from Shell:
Avail MEMF_CHIP
Avail MEMF_FAST |
Chip memory should rise only by AHI buffer allocation (two chip buffers in the sink). Fast memory should hold pipeline ring buffers and plugin queues; no large chip leaks after Play exits.
Failure modes
| Symptom |
Likely cause |
| cannot open fastforward.library |
Library not in LIBS: |
| unsupported or missing file |
Sniff failed or plugin not registered |
| No audio |
AHI not installed; wrong unit; empty PCM path |
| Immediate exit |
Demux header parse error; check file format |
Built-in vs external plugins
If only fastforward.library is installed with built-ins linked (dev smake), external plugin files are optional. For release, ship separate plugin files and a library without duplicate symbols.
Supported formats and plugin status
3. Implemented (MVP)
These four plugins plus the callback sink (built into the engine) form the current vertical slice: WAV / AIFF / 8SVX / 16SV → AHI.
| Plugin |
Kind |
Source |
Notes |
| file.source |
FFK_SOURCE |
FastForward Source/plugins/file_source.c |
BPTR / path byte reader; all file-based chains start here |
| (built-in) mem.source |
FFK_SOURCE |
Source/plugins/mem_source.c |
Memory buffer reader for previews and embedded media |
| pcm.demux |
FFK_DEMUX |
same + sniff logic in Source/library/ff_sniff.c |
WAV, AIFF, 8SVX, 16SV IFF containers |
| pcm.codec |
FFK_CODEC |
same |
Pass-through PCM (identity codec); format negotiation |
| ahi.sink |
FFK_SINK |
same |
AHI output via ahi.device; chip RAM sample buffers |
| (built-in) callback.sink |
FFK_SINK |
Source/library/ff_callback_sink.c |
Delivers frames to app FFCallbacks; not a LoadSeg file |
18. Implemented plugins (status)
These plugins live in Source/plugins/ and build via its smakefile. The list grows as plugins land in tree.
| Plugin |
Kind |
Status |
Capabilities |
| file.source |
FFK_SOURCE |
done |
Local file byte reader |
| pcm.demux |
FFK_DEMUX |
done |
IFF / WAV / AU container detection |
| pcm.codec |
FFK_CODEC |
done |
Endian-swap + bit-depth pass-through |
| ahi.sink |
FFK_SINK |
done |
AHI double-buffered playback |
| au.demux |
FFK_DEMUX |
done |
Sun / NeXT .au (S8 / S16BE) |
| null.sink |
FFK_SINK |
done |
Bit-bucket sink for benchmarking |
| volume.filter |
FFK_FILTER |
done |
PCM gain (S8 / S16 / U8) |
| mpegaudio.demux |
FFK_DEMUX |
done |
MP1 / MP2 / MP3 elementary stream framing, ID3v2 skip |
| mpeg.demux |
FFK_DEMUX |
done |
MPEG-1 / MPEG-2 Program Stream (audio + video PES split) |
| mpega.codec |
FFK_CODEC |
planned |
MPEG audio Layers I–III decode |
| helixmp3.codec |
FFK_CODEC |
planned |
MP3 Layer III decode (68k-optimised path) |
| mpeg2.codec |
FFK_CODEC |
planned |
MPEG-1 / MPEG-2 video decode |
Transport controls (tape-deck metaphor)
FastForward names its transport API after a tape deck. Play and applications using the Movie API expose the same controls:
Two metaphors that read the architecture out loud
FastForward leans on two metaphors that are already baked into the primitives we use, and they map cleanly onto two different reader populations.
Orchestral — for the timing layer
realtime.library itself is named for an orchestra, and our timing code follows suit. Authors of CAMD / OctaMED / Bars&Pipes plugins already think in these terms.
| Orchestra |
realtime.library |
FastForward |
| Conductor |
struct Conductor |
FFClock.fc_Conductor (the master timing source) |
| Player on the podium |
struct Player, CreatePlayerA |
FFClock.fc_Player plus per-element FFElement.fe_Player (each producer has its own Player slaved to the graph's Conductor) |
| Tempo marking on the score |
PLAYER_Tempo |
FFClock.fc_Tempo (BPM × 1000) |
| Beat / tick subdivision |
PLAYER_Ticks |
FFClock.fc_TickRate |
| External sync source (a click track, SMPTE feed, MIDI clock) |
PLAYER_ExtSync, ExternalSync() |
FFCT_ExternalSync + FFCT_PlayerHandle; see API.md §19 |
| The score itself |
(no equivalent) |
FFGraph — the rehearsed plan |
| One performer |
(no equivalent) |
FFElement — one player of one part |
| Their instrument's range |
(no equivalent) |
FFPad.fp_Caps, fp_Audio, fp_Video |
We deliberately do not rename FFGraph / FFElement / FFPad to orchestral terms. The framework is multi-medium — a video frame is not really a "phrase" — and Player / Conductor are already realtime.library types we would shadow. The metaphor names why we do what we do, not what we call our types.
Tape-deck — for the transport layer
The library is named after the FastForward button. The application- facing transport is, by design, exactly the buttons on a real tape deck. Docs/API.md §3 walks through these in code, but the mental model is:
| Tape deck button |
FastForward call |
| Play |
PlayFFMovie() |
| Pause |
PauseFFMovie() |
| Stop |
StopFFMovie() |
| Cue / scrub |
SeekFFMovie(time, flags) |
| Rewind to head |
RewindFFMovie() (macro: Stop + Seek 0) |
| Eject the tape |
EjectFFMovie() (alias of CloseFFMovie()) |
| Resume from pause |
ResumeFFMovie() (alias of PlayFFMovie()) |
| Fast-forward 2x |
FastForwardFFMovie() (sets FFMT_PlayRate = 2.0) |
| Reverse 1x |
ReverseFFMovie() (sets FFMT_PlayRate = -1.0) |
| Variable scan |
ScanFFMovie(rate) (signed Q16.16 — FF_RATE_FF_4X etc.) |
| Loop / autoreverse |
FFMT_LoopMode = FFLM_LOOP / FFLM_PINGPONG / FFLM_REGION |
These are deliberately macros, not new LVOs. They compose existing LVOs (PlayFFMovie, SeekFFMovie, SetFFMovieAttrsA) so the ABI surface stays small, and any plugin that honours FFMT_PlayRate participates automatically. Plugins that only support 1× playback silently stay at 1× and the engine posts FFEV_QOS so the UI can grey out the affordances that are not supported by the current codec.
The two metaphors complement each other: orchestral language is right at the timing boundary where FastForward already speaks realtime.library; tape-deck language is right at the application boundary where humans drive the deck. Inside the library we use neither — FFGraph, FFElement, FFPad, FFBuffer are technical names for technical concepts.
Scheduling decisions. Amiga unified memory means the framework can pass references everywhere except the final sink hand-off. The memory class hint (FFBM_FAST / FFBM_CHIP / FFBM_LOCAL / FFBM_ANY) on each FFBuffer keeps the decoder/filter chain in Fast RAM where the 68k is fastest, and only the AHI sink's DMA buffer lives in Chip RAM where Paula can read it without the CPU. Video sinks follow the same rule: chunky-to-planar conversion happens once into a Chip-RAM bitmap that the blitter and copper then consume directly.
Run modes. Every FFElement defaults to FFER_INLINE, so single-thread linear graphs (the MVP audio path) keep their existing behaviour with zero overhead. Heavy elements opt into their own Task by passing FFE_RunMode = FFER_TASK; sources and sinks that need dos.library calls use FFER_PROCESS (a CreateNewProc so Lock, Read, OpenDevice all work). Mixed graphs are normal: Play stays single-thread for cheap WAV decode; an NLE timeline runs each track as its own Task; a music sequencer attaches every producer to the master Conductor so audio, MIDI and video track heads stay sample-accurate.
Topology granularity on classic 68k. Exec context switching is cheap by 2026 standards — sub-millisecond on a stock 68030 — but it is not free, and a graph that puts every filter on its own Task spends measurable cycles on Signal / PutMsg / WaitPort per buffer. The recommended pattern on classic hardware is coarse- grained: keep FFER_INLINE as the aggressive default so a single caller-task slice runs Pull → Decode → Filter sequentially, and cross to FFER_TASK / FFER_PROCESS only at boundaries where you genuinely want the asynchronicity — disk I/O, the AHI / video sink, a CPU-heavy codec that wants its own pacing. Long pipelines of fine-grained nodes are an RTG / 68060+ luxury; on a stock 68030 they trade frame-rate for thread-count and lose. The graph builder honours whatever the application requests; it does not silently fold elements together.
Why not real process isolation. AmigaOS 3 has no MMU separation between Tasks; a buggy codec can scribble on the engine no matter how many message ports we put between them. Pretending otherwise would just slow us down. Defensive parsing (counted FFString / FFData everywhere a media file's bytes cross into the engine) is the realistic safety net. WarpOS PPC plugins do get effective crash isolation as a side effect of the PPC kernel boundary — see TODO.md.
See the Overview for architecture and supported formats. See the Developer’s Guide to build applications.
|
Back to top
|
|