Saturday, December 08, 2007

MIDI control of monitor source

These are the changes needed to implement MIDI control of monitor source:

in MidiInfo.h:

enum { // plugin MIDI properties
MP_BYPASS,
MP_MONITOR,
PLUGIN_MIDI_PROPS
};

in MainMidi.cpp:

const int CMainFrame::m_PluginMidiPropName[PLUGIN_MIDI_PROPS] = {
IDS_MP_BYPASS,
IDS_MP_MONITOR
};

case MP_MONITOR:
if (Toggle)
Val = (GetMonitorSource() != ParmIdx);
{
bool IsMod = IsModified();
SetMonitorSource(Val >= .5 ? ParmIdx : -1);
SetModify(IsMod); // restore modify flag
}
break;

The above change also requires a new version of the project file format, otherwise errors will occur in CFFPlugInfo::Serialize due the increased size of m_MidiInfo. This probably could have been avoided if the size of m_MidiInfo had been serialized, but it's too late for that now. The only remaining option is an explicit version test. Note that the version being tested is from the next level up, i.e. it's a CFFProject version, not a CFFPlugInfo version. The CFFPlugInfo version must be increased too, so that the clipboard format changes.

// this was tested and works fine
int MidiInfoSize = Version > 4 ? sizeof(m_MidiInfo) : sizeof(CMidiInfo);
...
ar.Write(m_MidiInfo, MidiInfoSize);
...
ar.Read(m_MidiInfo, MidiInfoSize);

Thursday, September 27, 2007

Thumbnail thread exit problem

If we're in thumbnail view, the thumbnail thread could be running, and we want it to exit, so we can safely launch another instance. We ask it to exit by incrementing the job ID; the thread checks the job ID after each extraction, and exits if the ID changes. This simple method has a weakness: there's a chance we preempted the thread after it checked the ID, but before it posted the bitmap to us, in which case we'll receive a spurious bitmap message. The thread is almost always busy extracting, so the chance is very small, but just in case, we increment the job ID before doing other work, thereby giving the thread more time to exit.

Sunday, July 29, 2007

Projects are affected by changing frame rate

Frame rate has turned out to be a major hassle. It probably would have been a good idea for projects to store the frame rate that was in effect which the project was saved, so that if the project is subsequently run at a different frame rate, the oscillator frequencies could then be compensated. Oh well.

Practically speaking, all of the projects that are currently being prepared for DVD render correctly at 25 FPS ONLY. This includes

big hex 27%
UltraWhorld 3b
UltraWhorld 4b

Just to make things even more confusing, there's a problem with PeteKaleidascope on the XP machine: at Divisions = .03 (horizontal mirroring), it creates ghosts. This can be observed clearly with the UltraWhorld 4b patch. The solution is to use a mirror plugin instead.

Friday, July 13, 2007

Hidden controls retain focus; child dialogs and DS_CONTROL

Version 1.4.03 (and previous versions) crash deterministically if you load (via drag/drop) a plugin with at least one parameter, then load (again via drag/drop) a plugin with NO parameters, and then spin the mouse wheel, or press one of the arrow keys or editing keys. This turns out to be an example of a more general problem: if a control has focus, hiding its parent dialog does NOT take focus away from the control. The control continues to receive mouse and keyboard input. This can cause unexpected behavior (e.g. the wheel moving an invisible control) or even crash the app. But why do we have hidden dialogs? Here's why.

FFRend makes much use of the row view. This custom UI object is similar to a list view or grid control, but it's implemented as a form view containing a vertical list of child dialogs, one per row. A row dialog is just like any other dialog: it has a resource, contains controls, can be built using the Class Wizard, and can even be tested outside the containing view. The main advantage of this approach is encapsulation: row dialogs derive from a base class which makes it easier to operate on entire rows at once (e.g. for cut/copy/paste). By contrast, in a grid control, everything is contained in a single window, so there's no equivalent to a row object.

FFRend uses the row view to display plugin parameters, patchbay, MIDI setup, and metaparameters. In the case of plugin parameters, there's a complication: each plugin has different parameters, but only one plugin's parameters are visible at a time. FFRend allows each plugin to have its own set of row dialogs, and then shows and hides entire sets of row dialogs as needed. For example, when a plugin is selected, the previously selected plugin's rows are hidden, and the new plugin's rows are shown. This approach is wasteful in terms of memory, windows, and GDI objects, but efficient in terms of CPU usage: hiding/showing windows is cheap compared to creating and destroying them. The approach has another advantage: since we can assume that while a plugin exists, its parameter row dialogs also exist, we can store the parameter and automation data in the row controls, instead of storing them elsewhere and updating the controls on demand. This is elegant, and simplifies undo handling. So that's why we have hidden dialogs.

As it turns out, a dialog has to have the DS_CONTROL style in order to behave well as a child of another window. People often encounter this issue when they try to make a home-grown property sheet, i.e. a series of child dialogs that can be overlaid onto a parent container dialog. The usual problem is that without DS_CONTROL, tabbing doesn't work as expected: the entire child dialog is treated as a single tab stop. DS_CONTROL integrates the child dialog's tab layout into the tab layout of the parent window. FFRend used to handle tabbing in row views explicitly, but now that it's using DS_CONTROL, Windows handles the tabbing.

DS_CONTROL also improves window activation behavior: without DS_CONTROL, showing the parent window unexpectedly focuses the last control in the child dialog, and this can lead to the problem described above (hidden controls with focus). So DS_CONTROL is a very good discovery, and makes the row view UI much more robust, but it's still necessary to test for a hidden control with focus after updating the plugin parameters view. That's not such a big deal.

Monday, June 18, 2007

XP/Uno drops input MIDI running status messages

The problem occurs when using WhorldRC to switch video clips quickly in the PlayerFF plugin. The problem first appeared when shortcut keys were added to WhorldRC for triggering recent clips. Before that it wasn't possible to switch clips fast enough to cause the bug. It seems that when MIDI messages are output close together in time, Windows uses MIDI running status, otherwise not. This was verified using the DOS mididump program. The running status messages are NOT received by the MIDI input callback under XP with the M-Audio Uno. This is true using both the driver that shipped with the Uno, and the Windows default driver. Note that with the latest M-Audio driver (MA_CMIDI_WDM_4.2.03v4.exe), the callback DOES receive events for the running status messages, but they are strangely garbled.

Attempts to reproduce the problem using the X-Session as input were not succesful. Perhaps it's the particular nature of WhorldRC's output (b0 00 xx b0 01 xx) that causes the bug? The most likely suspects are the Uno itself, its driver, or XP's USB MIDI driver. One way to test this would be by using a different MIDI device.

For the moment the workaround is a hack to WhorldRC: it sends a note off command after each set of bank/clip commands commands. This is a bit wasteful but WhorldRC is a low-bandwidth app and it seems to have no side effects.

Saturday, February 03, 2007

meta-meta-meta-plugins

The MetaFFRend beta is available now. We actually had a bit a of a celebration here tonight. Not only does MetaFFRend work, but we proved that nesting is unlimited. It's easy to make a meta-metaplugin, a metaplugin that contains metaplugin(s) which contain plugins. Or even weirder combinations, like a metaplugin which uses a mixer plugin to do automated mixing between two metaplugins. (!!) The level of nesting is arbitrary, and there's essentially no performance penalty for it, because the overhead of the base plugins is always huge compared to the overhead from nesting.

This is wild stuff, and not so impractical as it might sound. It means if I make a metaplugin that does something you like, and send it to you, you can use it inside one of your own metaplugins, add value to it somehow, and then send it someone else. By its very nature MetaFFRend encourages iterative composition, and distributed creation. It also means that you can simplify your own creative process by using grouping, in other words you can organize your own metaplugins in a hierarchy, with more basic effects at the bottom, e.g. a kaleidescope with automated rotation, and then reuse your building blocks (or other people's) in higher-level metaplugins.

Not only that, but embedding can be nested too, though ONLY if all the metaplugins at each level are "free", i.e. include the word "copyleft" in their author/license field. So for example if I send you one of my embedded metaplugins, and it's copyleft, you can embed it inside one of your metaplugins, and if you specify copyleft too, your metaplugin can also be embedded by someone else, and so on... And at each level the embedding works as expected, so the final plugin can be loaded into a host like any other plugin, and automatically unzips itself recursively the first time you load it. None of the recipients needs to worry about missing plugins. How about that.

The real point here is that it gives you, me, all of us a super-compact way to transport compositions. A complicated metaplugin might be 300K bytes, WITH embedding, without, less than 100K. Compare this to the cost of transporting a 30-second movie file, even compressed. The problem I've been trying to solve is that while Whorld operates in vector space, as soon as I apply effects to it, the output is video, which is a huge storage problem. But now I don't need to store anything. Instead of making a movie, I just send you a metaplugin that contains the Whorld plugin, running through a bunch of effects with automated parameters. It looks THE SAME as it did on my machine, but no goddamn gigantic movie file, and no compression either. You can render it to a movie file if you want, at any resolution you like, and if I used randomness in my Whorld patches (or in my automations), it will do something different every time you run it. The static movie file is replaced by an algorithm that's capable of dynamic behavior, user interaction, etc. Visual effects can be made quickly, without programming, even made live, and then easily transported, to be used and/or modified by others.

What this really does is push Whorld (and any other freeframe visual synths) back into vector space. It's a whole new type of content, all the more because it's N-dimensional (due the nesting capability), and distinct from making videos or coding plugins or VJing. It's metaplugin authoring, I don't know what else to call it, but hey, I'm drunk. Whatever it is, it's wicked object-oriented.

OK I'll shut up now and go drink some water. My head is starting to hurt and not just from programming.

Friday, January 19, 2007

RFC: MetaFFRend / FF meta-plugin authoring

Request for Comments

OK, now that FFRend 1.3 is out the door, it's time to get serious about MetaFFRend. I hope to spark a bit of pre-coding discussion here, so that once the coding starts we can get it mostly right the first time. Here are some issues that have been bumping around in my head.

Summary

This is a Request For Comments regarding the VJ Forums proposal to turn FFRend into a plugin-authoring tool, somewhat similar to Pete Warden's FreeChain application. The idea is basically to export a FFRend project as a Freeframe plugin. This "meta-plugin" could be loaded into a Freeframe host software, and would behave just as if you were running the equivalent project in FFRend.

The meta-plugin may contain links to the plugins it requires, or alternatively, the plugins may be "bundled" into the meta-plugin. In the latter case, it's easier to distribute the meta-plugin, because there's no dependence on external files. Specifically, a meta-plugin will consist of FFRend's rendering engine, links to (or compressed copies of) the necessary plugins, and the project data (parameter settings, automations, etc).

Linked plugins and bundling

A meta-plugin contains links to other plugins. This works fine as long as the meta-plugin stays on the PC where it was created, but as soon as you distribute it, you have problems. There's very little chance that the absolute paths will happen to be correct on someone else's PC. IMO the solution has two parts. The first part is, the meta-plugin should search for its plugins in an intelligent way, e.g. the following steps, in order:

1) Try the absolute paths from the project data first.
2) Then look for an INI file, e.g. My Documents\FFRend\MetaFFRend.ini, which could contain a plugin path, e.g. PluginPath=C:\whatever;C:\foo;
3) Next, try My Documents\FFRend\Plugins.
4) Finally, look in the same folder the meta-plugin was loaded from.
5) If none of the above works, add an error message to a log file, e.g. My Documents\FFRend\MetaFFRend.log

The second part of the solution is, there should be an option to bundle all the plugins that the meta-plugin requires into the meta-plugin. This could make the meta-plugin quite large, so it probably makes sense to compress the plugins, e.g. using zlib which is open-source. The first time you load a bundled meta-plugin, it will unpack itself to the folder specified in MetaFFRend.ini, or if that fails, to My Documents\FFRend\Plugins. On subsequent loads it will detect that the plugins are already there, and skip the unpack. This means the meta-plugin could be slow to load the first time, but subsequent loads will be fast.

Meta-parameters

Like any other FF plugin, a meta-plugin can expose parameters to the host. These parameters are referred to as "meta-parameters". The consensus on this forum was that each meta-parameter should be able to control multiple things. When I say "things" I mean control targets; the potential targets in FFRend are not only FF parameters, but also modulator settings (frequency, waveform, etc.), bypass, and other properties. The reason why this is such an important capability, is that many hosts severely limit the number of FF parameters a plugin can expose. By allowing "grouping", the meta-plugin can make better use of a limited number of parameters.

Anatomy of a Meta-Plugin

The base MetaFFRend DLL (i.e. without any project data embedded in it) lives in the same folder as FFRend. It has a tag at the end of the DLL file, consisting of 16 characters: BASEMETAFFREND01

When FFRend creates a new meta-plugin, it starts with the base plugin. It removes the end-of-DLL tag and replaces it with the project data, followed by a different tag, also 16 bytes, like so:

char Id[10]; // must contain characters MetaFFRend
WORD Version; // version number
DWORD DataOfs; // offset of project data from end of DLL file

What's nice about this scheme is it's easy to read the project data from the DLL: you just read the last 16 bytes, and now you know whether it's a valid meta-plugin, and if so, where to find the project data. Project data is a CArchive, and starts with a CMetaPlugin header:

CString m_PluginName; // plugin name, 16 characters maximum
CString m_Description; // a description of the plugin
CString m_AboutText; // author and license information
int m_BitDepthMask; // mask of supported bit depths
int m_PluginMajorVersion; // number before decimal point
int m_PluginMinorVersion; // number after decimal point
int m_NumInputFrames; // number of input frames plugin expects
CDWordArray m_InpTargetIdx; // for each input frame, index of target sub-plugin, or -1 for default
bool m_IsBundled; // true if sub-plugins are bundled into meta-plugin
bool m_IsCompressed; // true if bundled sub-plugins are compressed
CMetaParamArray m_Param; // information about each meta-parameter

class CMetaParam:

CString m_ParamName; // parameter name, 16 characters maximum
float m_Val; // parameter's initial value
CMetaParamTargetArray m_Target; // array of parameter targets

class CMetaParamTarget:

int m_PlugIdx; // index of destination plugin, or -1 for misc. property
int m_ParamIdx; // index of destination parameter, or -1 for plugin property
int m_PropIdx; // index of property
float m_RangeStart; // start of parameter range
float m_RangeEnd; // end of parameter range