Thursday, December 22, 2005

hogging CPU breaks About dialog

If main is saturating the CPU, Help/About makes the app modal, but no dialog is displayed. The bug occurs with any dialog created via DoModal, but it does NOT occur with the common dialogs (e.g. CFileDialog), presumably because they use a different creation method. Help/About is currently the only non-common modal dialog accessible directly from the main menu, which is why the bug wasn't discovered earlier.

One workaround is to disable the main timer in CAboutDlg::OnInitDialog, and then re-enable it in ShowWindow. An easier one is to do an explicit ShowWindow in CAboutDlg::OnInitDialog.

Wednesday, December 21, 2005

changing hue loop length

Changing the hue loop length without compensation causes distracting jumps in hue. Reflecting the hue position prevents jumps entirely when the loop length is increased, and minimizes the probability of a jump when the loop length is decreased. Without changing the base position, it's impossible to prevent jumps entirely in the latter case, because the current hue may no longer fall within the range of the loop.

if (m_st.HueLoop && m_st.HueLoopLength) // try to avoid hue jump
m_st.HueLoopPos = Reflect(m_st.HueLoopPos, m_st.HueLoopLength);
m_st.HueLoopLength = Length;

Friday, November 11, 2005

limiting random origin motion

double RandOrgRange = 1; // 0 to 2, default is 1
DPOINT RandOrgBias = {0, 0}; // -.5 to .5, default is 0
double ofs = (1 - RandOrgRange) / 2;
m_TargetOrg.x = double(rand()) / RAND_MAX
* RandOrgRange + ofs + RandOrgBias.x;
m_TargetOrg.y = double(rand()) / RAND_MAX
* RandOrgRange + ofs + RandOrgBias.y;

Wednesday, November 09, 2005

seed of life

It's dependent on the frame rate. Frame rate, birth rate, ring spacing, and skew angle delta interact to determine how many ring tunnels are generated.

If birth rate = 1, ring spacing = 5, and skew angle freq = 1, at 25 FPS you get 5 ring tunnels (a 5-way seed of life), because you're generating 5 rings per second, and each ring has a fifth of the skew angle delta (.2 Hz = 72 degrees), and 5 clock ticks.

at 30 FPS, with the same settings, you get a 6-way seed of life.

Color speed can also be adjusted to achieve a constant color for each tunnel. 14.4 (a fifth of 72) works for the 5-way at skew angle = 1; 90 works for most of the others.

Since the rings continue to intersect at the origin no matter how big they get, the rings will never be entirely off-canvas, and thus will never be deleted. This is a dangerous side effect and yet another argument for a max rings parameter.

Useful formulas:
Birth Ring Skew ring color
FPS Rate Space Ang Hz tunnels speed
25 .25 5 1.25 1 90
25 .5 5 1.25 2 90
25 .75 5 1.25 3 90
25 1 5 1.25 4 90
25 1.25 5 1.25 5 72
25 1.5 5 1.25 6 90
25 1.75 5 1.25 7 90*
25 2 5 1.25 8 90*
25 2.25 5 1.25 9 90*
25 2.5 5 1.25 10 72*

*colors are stable but tunnels don't appear in spectral order

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.

Wednesday, September 28, 2005

movie disk requirements

The data rate of a WHM movie file is linearly proportional to scene complexity (as measured by ring count), and is also affected by the frame rate. Typical data rates range from .4 - 3.0 MBytes @ second, which means a 1-hour recording can require anywhere from 1.5 to 10 GB.

Both ATA 100 and Firewire A can sustain write data rates of at least 30 MB @ second, with minimal CPU loading, so performance probably isn't an issue. Disk space could be an issue, particularly on older machines.

Using WinZip on a WHM file typically yields a 60% - 70% size reduction. Recording to a compressed folder yields a 40% - 60% reduction, but causes the display to freeze at regular intervals on a PIII machine. The Gnu zlib compression library might have less impact, but some increase in CPU load is inevitable. To reduce disk usage, it might make sense to compress WHM files after recording. The app could include an option that does this automatically, but it would have to be done from a separate low-priority thread, and even then performance might be affected by context switching.

A higher-order solution would record user input instead of recording ring data. This would reduce the data rate to a negligible amount, but at the expense of greatly increased complexity and fragility. This method should be explored for a future version.

A very rough estimate of the data rate resulting from input recording:

Record cursor delta for every frame: 8 bytes (2 ints) @ frame
Record 16 MIDI controllers for every frame (worst case): 48 bytes @ frame
Keyboard commands are too infrequent to affect the estimate
8 + 48 = 56 bytes per frame; at 30 FPS, 56 * 30 = 1680 bytes @ second
That's 6MB per hour in the absolute worst case

Some benchmarks:

Uncompressed 1024 x 768 video at 30 FPS: 70.5 MB @ sec
Lotus light, speed around 700, 30 FPS, 1 minute = 131 MB (2.2 MB @ sec)
Cross, speed around 700, 30 FPS, 1:36 = 80 MB (.83 MB @ sec)
Kaleid, speed around 700, 30 FPS, 1:36 = 282 MB (2.9 MB @ sec)

Another interesting possibility: Recording the view's oscillators and STATE member but not the rings, and then regenerating the rings on playback/export

1 oscillator = 44 bytes * 16 = 704 bytes @ frame
view STATE member = 104 bytes + 704 = 808 bytes @ frame
call it 1K bytes @ frame, at 30 FPS = 30K bytes @ second (108 MB @ hour)

Thursday, September 22, 2005

making a WMV-9 DVD

Toshi says no need to change frame rate for WMV; between 5 and 20 MBits per sec should be good, and 1024 x 768 is fine. Must compress the bitmaps first to make enough room to create an AVI, expect on the order of 75% compression overall.

Monday, September 12, 2005

additional MIDI control

on/off: mirror, fill, outline, xray, invert hue, invert fill, invert outline, invert xray, rotate hue, reverse

zoom should be damped, no?

Saturday, September 10, 2005

show size in status bar message while resizing

You must handle WM_ENTERSIZEMOVE and wM_EXITSIZEMOVE to show the initial size and restore the default message.

LRESULT CMainFrame::OnEnterSizeMove(WPARAM wParam, LPARAM lParam)
CRect r;
CString s;
s.Format("%d x %d\n", r.Width(), r.Height());
SetMessageText(s); // must do all the above in OnSize too, but only if window is visible

LRESULT CMainFrame::OnExitSizeMove(WPARAM wParam, LPARAM lParam)

Note that this only works if the system property "show window contents while dragging" is set. Otherwise you only see the initial size, because WM_SIZE isn't sent until the drag ends. WM_SIZING *is* sent, but it doesn't help, because it passes us the size of the frame, whereas we want the size of the view. Oddly, there seems to be no way to determine the client area that would result from a given window size. CalcWindowRect does the reverse: it gives us the size of the window needed for a given client area.

Tuesday, September 06, 2005

CalcWindowRect bug

This bug first appeared during the development of movie export. Moving the export to a separate process made it go away, but now it's back in version 1.0.73. Most likely the AVI file DLL was falsely accused and the movie export could have been part of the main app, but that's a secondary issue.

The symptom is, the app crashes in RowDialogForm's CalcWindowRect during the construction of the Parms dialog. It doesn't crash in debug, only in release, and creating a console window makes it go away (great!). Deleting the unused m_Template member variable from CPersistDlg.h also makes it go away. This last symptom clearly points towards a memory corruption problem of some kind. In the release debugger, the problem begins when CRowDialog::OnInitDialog calls CViewDialog::OnInitDialog. After this call, CRowDialog's "this" and HWND are trashed. Note that neither CViewDialog nor its immediate base class (CPersistDlg) override OnInitDialog, so this is effectively a call to CDialog::OnInitDialog.

Removing the call to MakeVJAccelTable in CMainFrame's ctor also makes it go away.

The really bad news: the app only crashes when you execute it from the IDE; running from Explorer is OK.

Other observations:
in Frame's ctor list, m_ResultsDlg(m_View), is incorrect initialization, m_View isn't the dialog's parent and in any case it's NULL! deleting this line doesn't help though...

could be passing 'this' to dialogs isn't such a good idea after all, seems like CMainFrame's 'this' keeps changing somehow or is that just the debugger being bad in release mode?

The solution: in CalcWindowRect, casting GetParent() down to CRowDialog isn't always correct. Initially, the parent is the dummy frame, not CRowDialog. Use a dynamic downcast to determine the parent.

The moral of the story: Downcasting is dangerous so use dynamic downcast and verify the result!

Monday, August 29, 2005


pjbmp2avi.exe seems to work fine, with the CinePack codec at 100% (default) it's about 11MB for 36 seconds.

Storing snapshots with window size at 720x480 (DVD fullscreen) yields 80K snaps on average, with no appreciable slowdown: 80 * 25 * 60 = 120MB @ minute. This seems better than storing bitmaps.

Actual date rate test: demo speed = 975, 18.4 minutes, size 1.256 GB, rate = 73MB @ minute

Random waveform bug can be demonstrated using new OscTest, settings: random, .01, .5, .05, .1

Sunday, August 28, 2005

almost there

During crossfade, random waveform's frequency change glitches horribly, fix this. Also fix a minor glitch when patch is switched without crossfade: in this case clear the view's previous parameter info to prevent interpolation. Ring growth and master speed glitch too but it's less noticeable.

Overall it looks gorgeous and feels incredibly powerful and exciting, it's a different whorld. Master speed of 901 is very nice, maybe use this speed for demo? Maybe a bit too fast, 603 is good too...

Random waveform bug can be demonstrated using new OscTest, settings: random, .01, .5, .05, .1

Saturday, August 27, 2005

more on clumping

oscillator must move into view so it can be subclocked (non-integer clock increments). See 1.0.69c.

Wednesday, August 24, 2005


The clumping problem goes all the way back to the beginning. It occurs when master speed and/or ring growth) are big enough to cause multiple rings per timer tick. When the view's timer hook adds multiple rings, the rings all share the same properties, which causes those rings to form an easily distinguishable group or "clump".

The worst offenders are rotation and skew (AKA shift). Both of these are zeroed at birth, and then incremented by the view's draw function. A major reduction of clumping can be obtained simply by offsetting each ring's initial rotation and skew. The offset is a delta multiplied by a fraction. The fraction is calculated by dividing the ring's index by the total number of rings that will be born for this timer tick. If four rings will be born, the fractions for the rings will be 0, .25, .5, and .75 respectively, and if the current rotation delta is .2, the initial rotations for the rings will be 0 * .2, .25 * .2, .5 * .2, and .75 * .2.

The only complication is that the rings are generated in reverse order. This means the indices have to be inverted, e.g. by using (count - 1 - i) instead of i, which means the count must be computed before the ring-adding loop begins. The count can be computed as:

int(m_st.RingOffset / m_Parms.RingSpacing) + 1;

Also note that the resulting offsets must be multiplied by RingGrowth to be effective.

The next-worst offenders are the LFOs. The LFOs are outside the view, but they only modify the view parameters on timer tick boundaries. This means that any LFO has the potential to cause clumping. The high road would move the oscillators into the view but this is a drastic change and has many side effects. The low road is to interpolate between the current and previous parameter values. In practice interpolation seems to work very well for all parameters except ring growth and ring sides. The view timer hook must calculate a delta between each parameter's current and previous values, like so:

for (int j = 0; j < 16; j++)
delta[j] = (((double *)&prevparm)[j] - ((double *)&m_Parms)[j]) / count;

And then within the ring-adding loop, overwrite m_Parms with interpolated values:

for (j = 0; j < 16; j++) // NOTE: don't do this to ring growth or # sides
((double *)&m_Parms)[j] = ((double *)&prevparm)[j] + delta[j] * (count - 1 - i);

And finally, restore m_Parms.

Interestingly, this seems to work perfectly with sine LFOs, but exhibits a noticeable periodic discontinuity with triangle LFOs. This seems to indicate an undiscovered problem with the above method, or possibly a bug in the oscillator object.

The final offender is the ring color. This is calculated in the view's timer hook, which means all the rings in a group will have the exact same color. This can probably be solved by interpolation as well.

Thursday, August 18, 2005

front page image

canvas size to 1280x700
save for web at 50% jpeg high
result 640x350 +/- 35K

Saturday, August 06, 2005

reverse and per-ring draw mode

In reverse, innermost ring fill test must be steps < minimum size AND steps < previous radius. This prevents possible holes when switching from forward to reverse, e.g. in lotus light.

The new freedoms must have a price, let's see:

250 iters of default patch, total time spent in Draw:



It got faster? Not likely. Try again using 250 iters of Pinwheel No Rand:



Very strange. Try a longer test? 1000 iters, window maximized, Pinwheel no rand with Ring Growth = 3 and Canvas Size = 300:



1.0.61 (no reverse or per-ring draw at all)

Well, somehow it got faster...

mouse sensitivity

Overall much better now that ranges are correctly accounted for.
Speed, zoom and hue all feel about the same.

Trackball is too twitchy at sensitivity = 100%, it feels best around 65%.
Maybe nominals should be lower? Try some other devices and average the results.

Saturday, July 23, 2005

edit slider

can be used in:
x-fader dialog, for pos
row dialog? for val
master dialog, for all except damping?

Friday, July 22, 2005

line width: DC pen optimization

Don't re-select the DC pen when it's already selected: the GetStockObject and SelectObject calls are considerably more expensive than the test that avoids them.

Benchmark comparing wide-line version to original (default patch, total time spent in Draw function, 1000 calls) reveals that the wide-line version takes 0.06% longer:

old way:
AVG = 1.3430

new way:
AVG = 1.3439

#include "benchmark.h"
#define MAXSAMPS 1000
double samp[MAXSAMPS];
int samps;
bool done;
void CWhorldView::Draw(HDC dc)
Benchmark b;


if (samps < MAXSAMPS) {
samp[samps++] = b.Elapsed();
} else {
if (!done) {
FILE *fp = fopen("test.txt", "w");
double sum = 0;
for (int i = 0; i < samps; i++) {
fprintf(fp, "%f\n", samp[i]);
sum += samp[i];
done = 1;
CString s;
s.Format("%f", sum);

another test, using Pinwheel but with # sides set to 20 and no LFO, 2500 samples

new 21.6328
old 21.5634
0.32% slower

new 21.6226
old 21.5317
0.42% slower

line width

creating a 1-pixel wide CPen and selecting it takes 85 micros (avg)
best case is around 8 micros, worst case is 850 to nearly 1000 (!)

m_Pen.CreatePen(PS_SOLID, 1, rp.Color);
SelectObject(dc, m_Pen);

setting DC pen color and selecting DC pen takes 2 micros (avg)
best case is 1 micro, worst case is 5

CPen way is anywhere from 2 to 1000 times slower
it's worth it to branch if you plan on doing a lot of 1-pixel wide lines

Wednesday, July 20, 2005

lotus cross

lotus cross looks cool with these master settings:
speed 458
zoom 54
damping 51
tempo 20

Monday, July 18, 2005

Alt (menu) key

BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
// in full screen mode, ignore the Menu key, so we don't go modal
if (pMsg->message == WM_SYSKEYDOWN && pMsg->wParam == VK_MENU && m_IsFullScreen)
return CFrameWnd::PreTranslateMessage(pMsg);

Friday, July 08, 2005


zoom can position origin off-canvas, resulting in no rings

in SetNormOrigin, must clamp:

m_Origin.x = CLAMP(round( * m_st.NormOrg.x), m_Canvas.left, m_Canvas.right - 1);
m_Origin.y = CLAMP(round( * m_st.NormOrg.y),, m_Canvas.bottom - 1);

Tuesday, July 05, 2005

Optimizing message order in a message map is useless

MFC caches the most frequently used messages anyway, to avoid repeated table searching.

Saturday, July 02, 2005

mouse damping

Zone rings does damping from the timer hook: It repeatedly computes the difference between the origin and the mouse cursor, multiplies the difference by a normalized damping factor, and adds the result to the origin. The damping factor ranges from 0..1, 0 = maximum damping, 1 = no damping. At damping = 1, the result is always equal to the entire distance between origin and cursor, so the origin jumps immediately to the cursor position. At damping = 0, the origin would never move at all. Maybe the damping factor is kept above zero to prevent this.

Saturday, June 25, 2005

mercedes lissajous

mercedes looks good with these lissajous values:

delta .01
delta2 .02
phasedelta .0211
rad .25
origin .35
bit too fast though...

also nice colorspeed effect:
colorspeed 3.39 pulse wave amp = 2 freq = .05 pw = .333

lissajous origin

constant phase difference: affects width of oval, difference of PI/2 gives a line

linear phase difference change:

x = sin(theta + phasetheta);
y = cos(theta)


x = sin(thetax);
y = cos(thetay);

are equivalent forms, assuming thetax has a different delta than thetay. What's important is the *ratio* of theta to phasetheta (or thetax to thetay). When the ratio is a simple fraction, the shape is simple and predictable; ratios that aren't simple fractions (e.g. .51) tend to produce elaborate net-like shapes.

trig phase difference change:

x = sin(theta)
y = cos(theta + sin(phasetheta));

Again, it's the ratio between the deltas that matters. Incrementing theta by .1 and phasetheta by .01 will have the same effect as .2 and .02.

static double rad = .25;
double ox = sin(theta + sin(phasetheta) + i);
double oy = cos(theta2);
m_View->SetNormOrigin(ox * rad +.35, oy * rad +.35, FALSE);
theta += .01;
theta2 += .02;
phasetheta += .0311;

slower and better:
static double rad = .25;
double ox = sin(theta + sin(phasetheta));
double oy = cos(theta2);
m_View->SetNormOrigin(ox * rad +.35, oy * rad +.35, FALSE);
theta += .001;
theta2 += .002;
phasetheta += .00211;

Saturday, April 30, 2005

demo crossfade

randomize phase of parms whose frequency goes from zero to nonzero first, in A and B if freq AND amp are zero, set freq to 1e-6 so phase change will stick

for (int i = 0; i < ROWS; i++) {
if (!m_CrossDlg.GetInfo(reverse ? CCrossDlg::SEL_B : CCrossDlg::SEL_A).m_Row[i].ModFreq
&& m_CrossDlg.GetInfo(reverse ? CCrossDlg::SEL_A : CCrossDlg::SEL_B).m_Row[i].ModFreq) {
m_Osc[i].SetPhase(double(rand()) / RAND_MAX);
printf("randomized %s\n", ParmInfo::GetRowData(i).Name);

Friday, April 22, 2005

corrected ring deletion: cascade delete

In previous versions, deleting a ring also deleted all subsequent rings in the list. This was only perceptible when the erroneously deleted rings happened to be visible. This situation occurs in certain demo patches which use a large star factor.

demo patches affected:

drastic change:
cross Y totally different; occasional nice moire but mostly too cluttered
lotus light Y rings persist too long and spoil the drama
mercedes Y rings persist too long and spoil the drama

slight change:
pinwheel fab color Y "tunnel" state clears differently, but it's fine
pinwheel Y occasional very slight difference at edges
shimmer Y occasional slight difference at edges
tentacles Y slight difference: occasional stray threads hang around

no change:
good2 N no difference
kaleid N no difference
real pinwheel N no difference

"all at once" cascade:
while (NextPos != NULL) { // delete remaining rings
CurPos = NextPos;

gradual cascade, similar to original behavior, but smoother:
// main loop
DelPos = NextPos; // start cascade delete
// end of main loop
if (DelPos != NULL) {
RING& rp = m_Ring.GetNext(DelPos);
rp.Steps = 1e9; // mark ring for deletion