Thursday, October 27, 2005

objects needing more comments

ctrlresize, replacefilesdlg, masterdlg, parmrow, playlist, numbersdlg, viewdialog

Tuesday, October 25, 2005

adding HLS color to existing snapshots

ar << sizeof(RING);
ar << m_Ring.GetCount();
POSITION pos = m_Ring.GetHeadPosition();
for (int i = 0; i < m_Ring.GetCount(); i++) {
RING rp = m_Ring.GetNext(pos);
double h, l, s;
int c = rp.Color;
CHLS::rgb2hls(GetRValue(c) / 255.0,
GetGValue(c) / 255.0, GetBValue(c) / 255.0, h, l, s);
rp.Hue = h;
rp.Lightness = l;
rp.Saturation = s;
ar.Write(&rp, sizeof(RING));

Monday, October 24, 2005

Movie format could save space

The movies are using the snapshot format, which includes all of the ring info. In the ring info, the rotation and shift deltas and the HLS color are not used at all during movie playback. These attributes constitute 40 bytes out of 120, or one third of the total size of the ring structure. The actual savings could be nearly a third, since most of the space in a movie is taken up by rings (hundreds of rings per frame).

The savings would require adding a second serialization method, e.g. MiniSerialize, which would be used by the SnapMovie object. The method could be optimized by placing the deltas and the HLS color at the start of the ring structure, because then it would still be possible to do a single write directly from the ring, instead of copying to an intermediate structure first.

A problem with this approach is that it would longer be possible to export a complete (non-mini) snapshot from a movie, since some of the information would be missing. The HLS color could be restored from RGB, but not the deltas. On the other hand the deltas aren't useful in snapshots so maybe it wouldn't matter.

NOTE that WinZip reduces Steve1.whm by 61%, and Steve2.whm by 73%.

Space-saving benchmarks:

60 seconds, 25 FPS, default speed, window maximized but not full-screen, wait for full ring count before starting to record, sizes in MB, ring counts are rough averages

patch rings old new less
kaleid 1000 172.5 108.8 37%
pinwheel 500 98.0 62.5 36%
lotus light 300 71.4 46.5 35%
cross 150 34.6 26.3 24%

useful nugget of ring conversion code:

nr.RotDelta = rp.RotDelta;
nr.ShiftDelta = rp.ShiftDelta;
nr.Hue = rp.Hue;
nr.Lightness = rp.Lightness;
nr.Saturation = rp.Saturation;
nr.Rot = rp.Rot;
nr.Steps = rp.Steps;
nr.Scale = rp.Scale;
nr.Shift = rp.Shift;
nr.StarRatio = rp.StarRatio;
nr.Sides = rp.Sides;
nr.Delete = rp.Delete;
nr.Reverse = rp.Reverse;
nr.Color = rp.Color;
nr.Pinwheel = rp.Pinwheel;
nr.LineWidth = rp.LineWidth;
nr.DrawMode = rp.DrawMode;
nr.Spacing = rp.Spacing;

Thursday, October 20, 2005

array of CArrays not optimized correctly

It looks like dereferencing an array of CArray objects can causes MFC to call the [] operator, instead of inlining it, even in Release mode. Presumably this is why:

int CPlaylistDlg::FindHotKey(DWORD HotKey) const
int Patches = GetCount();
for (int i = 0; i < Patches; i++) {
if (HotKey == m_Bank[m_CurBank][i].m_HotKey)

generates twice as much code as:

int CPlaylistDlg::FindHotKey(DWORD HotKey) const
int Patches = GetCount();
for (int i = 0; i < Patches; i++) {
if (HotKey == (*m_Patch)[i].m_HotKey)

Oddly, this is bad too:

int CPlaylistDlg::FindHotKey(DWORD HotKey) const
int Patches = GetCount();
const PATCH_LIST *p = &m_Bank[m_CurBank];
for (int i = 0; i < Patches; i++) {
if (HotKey == (*p)[i].m_HotKey)

but this is fine (1 line longer than original):

int CPlaylistDlg::FindHotKey(DWORD HotKey) const
int Patches = GetCount();
for (int i = 0; i < Patches; i++) {
if (HotKey == m_Bank[m_CurBank].GetData()[i].m_HotKey)

Saturday, October 08, 2005

MainFrame bloat

2662 lines and counting, ouch
non-message handlers 1139
message map 166
message handlers 1200

things that could move easily:
ShowDemo: only 32 lines but could get bigger

not so easy
GetInput: 83 lines and could get bigger

odd shift

double xshift[2] = {rp.Shift.x * m_st.Zoom, (rp.Shift.x + rp.OddShift.x) * m_st.Zoom};
double yshift[2] = {rp.Shift.y * m_st.Zoom, (rp.Shift.y + rp.OddShift.y) * m_st.Zoom};

Notice that the calculation is NOT corrected for radius. This causes the effect to decrease with distance from the origin. Correcting for radius gives a very different and less pleasing effect. The display becomes too busy, and at extreme values, the image is distorted into a narrow cylinder. The first objection could possibly be addressed by limiting the size of the ring list.

To correct the above for radius:
double xshift[2] = {rp.Shift.x * m_st.Zoom, (rp.Shift.x + rp.OddShift.x * rp.Steps) * m_st.Zoom};
double yshift[2] = {rp.Shift.y * m_st.Zoom, (rp.Shift.y + rp.OddShift.y * rp.Steps) * m_st.Zoom};

Some possible names for "odd shift":

Shear: incorrect usage
Extrude: incorrect usage
Tilt: too vague
Tunnel: too vague, also other parameters can produce this effect
Relief: too vague
Stamen: too obscure
Center Fold: misleading, the new version corrects for radius, see below
Splay: means legs spread at various angles, not various lengths
Deform: vague, not a noun, misleading negative connotations
Bias: misleading connotation of unfairness
Fold: vague, possibly misleading
Asymmetry: vague, too long a word

10/29/05 corrected for radius but also centered, much better

DPOINT splay = {rp.Splay.x * rp.Steps, rp.Splay.y * rp.Steps};
double xshift[2] = {
(rp.Shift.x - splay.x) * m_st.Zoom,
(rp.Shift.x + splay.x) * m_st.Zoom};
double yshift[2] = {
(rp.Shift.y - splay.y) * m_st.Zoom,
(rp.Shift.y + splay.y) * m_st.Zoom};

Controls should be polar as with Skew: Splay Radius, Splay Angle

Some interesting interdependent terms:
Contort: Twist, wrench, or bend severely out of shape
Pinch: To press, squeeze, or bind painfully
Buckle: Bending, warping, or crumpling; a bend or bulge
Rumple: An irregular or untidy crease
Crumple: To crush together or press into wrinkles; rumple
Crease: A line made by pressing, folding, or wrinkling
Wrinkle: A small furrow, ridge, or crease on a normally smooth surface
Pucker: To gather into small wrinkles or folds

And the winner as of 12/17/05 is:

totally cool demo: default patch, ring spacing 11.192, pucker radius -.406, pucker angle -65.88 / ramp up / 180 / .08, master speed 222

another one: patch_051028050234, rings = 75, pucker rad = .268, angle = ramp up / 180 / .5, speed = 495

optimized 12/19/05 (two less Zoom multiplications):

DPOINT pucker = {rp.Pucker.x * steps, rp.Pucker.y * steps};
DPOINT shift = {rp.Shift.x * m_st.Zoom, rp.Shift.y * m_st.Zoom};
double xshift[2] = {shift.x - pucker.x, shift.x + pucker.x};
double yshift[2] = {shift.y - pucker.y, shift.y + pucker.y};

ring list size limit

in AddRing, at the very end:
while (m_Ring.GetCount() > whatever) {
if (Ring.Reverse)

Bézier curves

in Draw:
#if 1
m_pa[i] = m_pa[0]; // close the shape
Polyline(dc, m_pa, sides + 1);
CPoint pt2[200];
int j = 0;
for (int i = 0; i < sides; i++) {
pt2[j++] = m_pa[i];
if (i & 1)
pt2[j++] = CPoint(m_pa[i].x, m_pa[i].y);
pt2[j++] = pt2[0];
PolyBezier(dc, pt2, j);

Saturday, October 01, 2005

scene rotation

in DefState:
0 // Rotation

in Addring:
Ring.Rot = Ring.RotDelta * Offset + m_st.Rotation;

void CWhorldView::Rotate(double Degrees, bool Repaint)
POSITION pos = m_Ring.GetHeadPosition();
double r = DTR(Degrees);
while (pos != NULL) {
RING& Ring = m_Ring.GetNext(pos);
Ring.Rot += r;
m_st.Rotation = fmod(m_st.Rotation + r, 2 * PI); // wrap to limit magnitude
if (Repaint)

Note that this causes unexpected behavior (addition, subtraction, cancelation) when rotation is inverse of LFO rotation. Also X/Y shifts are NOT rotated, so for example if Aspect Ratio is 2, the asymmetry will remain orthogonal instead of rotating. Solving this would require changes to the drawing code.

benchmarks for movie recording

PIII, Kaleid, 1024 x 768, 30 FPS, speed = 680, 2500 samps: min = .001197, max = .004851, avg = .001821, sdev = .000362

avg .002 = .06 @ second = 6% avg usage for recording
AMD, Kaleid, 1024 x 768, 30 FPS, speed = 680, 2500 samps: min = .000167, max = .000611, avg = .000322, sdev = 8.8329E-05

avg .0003 = .009 @ second = .9% avg usage for recording
AMD test repeated over 5 minutes: min = .000172, max = .005491, avg = .000485, sdev = .000129

all samples were < than 1 ms except for a single single spike of 0.005491 at 3:38

glitch #1: Jeff & Sean


1. Visible tearing of image: DirectX is supposed to care of this no? Would full-screen mode affect it? Possibly affected by frame rate and CPU loading.

2. Should sync to beat automatically.

3. Should support multiple trackballs and other input devices at once.


1. Support for MIDI notes would be helpful since he's using an Oxygen 8. Notes could act as toggles for the function keys.

2. Tempo nudge should NOT resync, or at least there should be an alternate nudge that doesn't resync. Sean agrees.

3. Rotation of the entire image (as opposed to rotate speed), especially via MIDI.

4. Zoom should go further out: can do this with MIDI.