amigazen AWeb 3 AmigaPython ToolKit unsui FastForward Engi Zen
FastForward

User’s Guide
FastForward User’s Guide
Install, play media, inspect files, and configure plugins

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

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


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

Sourcesfile.source for local files, http-live.source for HTTP-LS streams, rtsp.source for RTSP streams

Demuxerspcm.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

Codecspcm.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

Filtersvolume.filter, fade.filter

Sinksaudio.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/:

smake

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