List Comparison
@buzzelljason Yes. Well. Everything sent by a control rate object is a message.
The messages can be floats or symbols or lists.
Lists are a list comprising floats or symbols or a mixture of the two.
Items (atoms) in a list are separated by spaces.
It gets complicated.
If the first atom in the list is a float then the "list" tag is automatically applied, and then is silently ignored and dropped as it passes through the next object (except for [route list symbol float].......)
If the first atom in the list is a symbol then that symbol becomes the tag.... the list header..... and the list can be routed by that tag. But if the list passes through a [list] object then "list" is added (prepended) as the header. That is why [list trim] is used almost everywhere after a [list] object...... to remove the header and then be able to [route] by the original tag (if it was a symbol).
HINT.. If you install the Zexy library then [rawprint] shows you whether an atom is a "tag", 'symbol' or float....
Trying to work with vanilla OSC objects can be just guesswork without [rawprint].
mmm.pd
The messages arrive from [audiosettings] as a list which is a mixture of floats and symbols........ but the first atom is a symbol and there is no "list" tag.
[listdevices( returns a series of messages..... they are lists...... with the first atom "device"
That is why "device"....... a symbol.... and "0" or "1" can be routed. "1" could not be routed by [route 0 1] unless it is a float and "device" could not be routed by [route device] unless it is a symbol.
The device name is a symbol........ it can only be sent on in one hit because it is a symbol...... if it were a list then each part would be sent separately as a space normally "means" "next item".
But once it has passed through [route 0 1] it has lost its "symbol" tag. That can be put back by passing it through [symbol].
So the problem for [list-compare] is to create a symbol that is identical. Not easy as a message with component symbols will be treated as a list. Fortunately @ingox gave us a vanilla solution.... [concat].
And then of course to turn them both into lists for the comparison.
This will work........ this.zip
It can probably be simplified (and certainly @ingox will shortly post a more elegant solution.... ).
David.
Some basic Ofelia GUI elements...
btw. this is the game of life glsl shader from the patch above. does anyboby know how to convert it to a gl es shader (the main problem is that the gl es version that i use cannot handle arrays)?
// fragment shader
#version 120
uniform sampler2D Tex0;
uniform vec2 resolution;
uniform float lCell_0;
uniform float lCell_1;
uniform float lCell_2;
uniform float lCell_3;
uniform float lCell_4;
uniform float lCell_5;
uniform float lCell_6;
uniform float lCell_7;
uniform float lCell_8;
uniform float dCell_0;
uniform float dCell_1;
uniform float dCell_2;
uniform float dCell_3;
uniform float dCell_4;
uniform float dCell_5;
uniform float dCell_6;
uniform float dCell_7;
uniform float dCell_8;
vec2 rule[9] = vec2[9](
vec2(lCell_0, dCell_0),
vec2(lCell_1, dCell_1),
vec2(lCell_2, dCell_2),
vec2(lCell_3, dCell_3),
vec2(lCell_4, dCell_4),
vec2(lCell_5, dCell_5),
vec2(lCell_6, dCell_6),
vec2(lCell_7, dCell_7),
vec2(lCell_8, dCell_8)
);
int get(int x, int y) {
return int(texture2D(Tex0, (gl_FragCoord.xy + vec2(x, y)) / resolution).a);
}
void main() {
int sum =
get(-1, -1) +
get(-1, 0) +
get(-1, 1) +
get( 0, -1) +
get( 0, 1) +
get( 1, -1) +
get( 1, 0) +
get( 1, 1);
vec2 r = rule[sum];
if (get(0, 0) == 1) {
gl_FragColor = vec4(0., 0., 0., r.x);
}
else {
gl_FragColor = vec4(0., 0., 0., r.y);
}
}
math.h, float or double?
@morpheu5 the canonical supported pd only uses 32-bit floats in messages (and signals). the question of how to write/format externals that support both floats and doubles is an ongoing discussion afaik. Right now almost everyone uses "normal" pd w/32-bit floats, and externals generally aren't available for pd-double.
most architectures support doubles now, so t_float isn't based on the machine. (but I presume the reason supported pd uses 32-bit floats is so it can run on an abbacus)
However, there's nothing to restrict external developers from using doubles in their externals internally. It's just that when the message/signal goes back into pd it will be 32-bit.
I think functions like fmod are fine to use for both doubles and floats (I wouldn't be surprised if the compiler will just use fmodf instead if the inputs are floats)
But I don't know how far off pd-double is from being supported or used anyways, so right now
I'd say right now you don't need to worry about t_floats or t_samples being doubles at all. No externals support pd double, because I don't think the loading mechanism/convention has been completely fleshed-out yet. (and barely anyone uses pd-double)
Ofelia Volumetrics Example
I tried to create the shader for a 3d game of life (adapted from my working 2d game of life shader https://github.com/Jonathhhan/PDGameofLife-shaderVersion-):
#version 120
uniform sampler3D Tex0;
uniform vec3 resolution;
uniform float lCell_0;
uniform float lCell_1;
uniform float lCell_2;
uniform float lCell_3;
uniform float lCell_4;
uniform float lCell_5;
uniform float lCell_6;
uniform float lCell_7;
uniform float lCell_8;
uniform float lCell_9;
uniform float lCell_10;
uniform float lCell_11;
uniform float lCell_12;
uniform float lCell_13;
uniform float lCell_14;
uniform float lCell_15;
uniform float lCell_16;
uniform float lCell_17;
uniform float lCell_18;
uniform float lCell_19;
uniform float lCell_20;
uniform float lCell_21;
uniform float lCell_22;
uniform float lCell_23;
uniform float lCell_24;
uniform float lCell_25;
uniform float lCell_26;
uniform float dCell_0;
uniform float dCell_1;
uniform float dCell_2;
uniform float dCell_3;
uniform float dCell_4;
uniform float dCell_5;
uniform float dCell_6;
uniform float dCell_7;
uniform float dCell_8;
uniform float dCell_9;
uniform float dCell_10;
uniform float dCell_11;
uniform float dCell_12;
uniform float dCell_13;
uniform float dCell_14;
uniform float dCell_15;
uniform float dCell_16;
uniform float dCell_17;
uniform float dCell_18;
uniform float dCell_19;
uniform float dCell_20;
uniform float dCell_21;
uniform float dCell_22;
uniform float dCell_23;
uniform float dCell_24;
uniform float dCell_25;
uniform float dCell_26;
vec2 rule[27] = vec2[27](
vec2(lCell_0, dCell_0),
vec2(lCell_1, dCell_1),
vec2(lCell_2, dCell_2),
vec2(lCell_3, dCell_3),
vec2(lCell_4, dCell_4),
vec2(lCell_5, dCell_5),
vec2(lCell_6, dCell_6),
vec2(lCell_7, dCell_7),
vec2(lCell_8, dCell_8),
vec2(lCell_9, dCell_9),
vec2(lCell_10, dCell_10),
vec2(lCell_11, dCell_11),
vec2(lCell_12, dCell_12),
vec2(lCell_13, dCell_13),
vec2(lCell_14, dCell_14),
vec2(lCell_15, dCell_15),
vec2(lCell_16, dCell_16),
vec2(lCell_17, dCell_17),
vec2(lCell_18, dCell_18),
vec2(lCell_19, dCell_19),
vec2(lCell_20, dCell_20),
vec2(lCell_21, dCell_21),
vec2(lCell_22, dCell_22),
vec2(lCell_23, dCell_23),
vec2(lCell_24, dCell_24),
vec2(lCell_25, dCell_25),
vec2(lCell_26, dCell_26)
);
int get(int x, int y, int z) {
return int(texture3D(Tex0, (gl_FragCoord.xyz + vec3(x, y, z)) / resolution).r);
}
void main() {
int sum =
get(-1, -1, -1) +
get(-1, 0, -1) +
get(-1, 1, -1) +
get( 0, -1, -1) +
get( 0, 1, -1) +
get( 1, -1, -1) +
get( 1, 0, -1) +
get( 1, 1, -1) +
get(-1, -1, 0) +
get(-1, 0, 0) +
get(-1, 1, 0) +
get( 0, -1, 0) +
get( 0, 1, 0) +
get( 1, -1, 0) +
get( 1, 0, 0) +
get( 1, 1, 0) +
get(-1, -1, 1) +
get(-1, 0, 1) +
get(-1, 1, 1) +
get( 0, -1, 1) +
get( 0, 1, 1) +
get( 1, -1, 1) +
get( 1, 0, 1) +
get( 1, 1, 1);
vec2 r = rule[sum];
if (get(0, 0, 0) == 1) {
gl_FragColor = vec4(r.x, r.x, r.x, r.x);
}
else {
gl_FragColor = vec4(r.y, r.y, r.y, r.y);
}
But it does not work.
Perhaps I did something wrong with the shader, but I think it does not work because ofFbo() can not store 3d textures.
Not really sure about that...
Ofelia - using addons, GL_TEXTURE_3D and binding openGL functions
i think i learned how to integrate addons (not everything is working yet...).
this is what i added to ofxOfeliaPdBindings.h to integrate ofxVolumetrics:
class pdTexture3d
{
public:
pdTexture3d(){};
void allocate(int w, int h, int d, int internalGlDataType)
{
texture3d.allocate(w, h, d, internalGlDataType);
}
void loadData(unsigned char * data, int w, int h, int d, int xOffset, int yOffset, int zOffset, int glFormat)
{
texture3d.loadData(data, w, h, d, xOffset, yOffset, zOffset, glFormat);
}
void loadData(float* data, int w, int h, int d, int xOffset, int yOffset, int zOffset, int glFormat)
{
texture3d.loadData(data, w, h, d, xOffset, yOffset, zOffset, glFormat);
}
void loadData(unsigned short* data, int w, int h, int d, int xOffset, int yOffset, int zOffset, int glFormat)
{
texture3d.loadData(data, w, h, d, xOffset, yOffset, zOffset, glFormat);
}
void loadData(ofPixels & pix, int d, int xOffset, int yOffset, int zOffset)
{
texture3d.loadData(pix, d, xOffset, yOffset, zOffset);
}
void loadData(ofShortPixels & pix, int d, int xOffset, int yOffset, int zOffset)
{
texture3d.loadData(pix, d, xOffset, yOffset, zOffset);
}
void loadData(ofFloatPixels & pix, int d, int xOffset, int yOffset, int zOffset)
{
texture3d.loadData(pix, d, xOffset, yOffset, zOffset);
}
void bind()
{
texture3d.bind();
}
void unbind()
{
texture3d.unbind();
}
void clear()
{
texture3d.clear();
}
ofxTextureData3d getTextureData()
{
return texture3d.getTextureData();
}
private:
ofxTexture3d texture3d;
};
class pdVolumetrics
{
public:
pdVolumetrics(){};
void setup(int w, int h, int d, ofVec3f voxelSize, bool usePowerOfTwoTexSize=false)
{
volumetrics.setup(w, h, d, voxelSize, usePowerOfTwoTexSize);
}
void destroy()
{
volumetrics.destroy();
}
void updateVolumeData(unsigned char * data, int w, int h, int d, int xOffset, int yOffset, int zOffset)
{
volumetrics.updateVolumeData(data, w, h, d, xOffset, yOffset, zOffset);
}
void drawVolume(float x, float y, float z, float size, int zTexOffset)
{
volumetrics.drawVolume(x, y, z, size, zTexOffset);
}
void drawVolume(float x, float y, float z, float w, float h, float d, int zTexOffset)
{
volumetrics.drawVolume(x, y, z, w, h, d, zTexOffset);
}
bool isInitialized()
{
return volumetrics.isInitialized();
}
int getVolumeWidth()
{
return volumetrics.getVolumeWidth();
}
int getVolumeHeight()
{
return volumetrics.getVolumeHeight();
}
int getVolumeDepth()
{
return volumetrics.getVolumeDepth();
}
ofFbo & getFboReference()
{
return volumetrics.getFboReference();
}
int getRenderWidth()
{
return volumetrics.getRenderWidth();
}
int getRenderHeight()
{
return volumetrics.getRenderHeight();
}
float getXyQuality()
{
return volumetrics.getXyQuality();
}
float getZQuality()
{
return volumetrics.getZQuality();
}
float getThreshold()
{
return volumetrics.getThreshold();
}
float getDensity()
{
return volumetrics.getDensity();
}
void setXyQuality(float q)
{
volumetrics.setXyQuality(q);
}
void setZQuality(float q)
{
volumetrics.setZQuality(q);
}
void setThreshold(float t)
{
volumetrics.setThreshold(t);
}
void setDensity(float d)
{
volumetrics.setDensity(d);
}
void setRenderSettings(float xyQuality, float zQuality, float dens, float thresh)
{
volumetrics.setRenderSettings(xyQuality, zQuality, dens, thresh);
}
void setVolumeTextureFilterMode(GLint filterMode)
{
volumetrics.setVolumeTextureFilterMode(filterMode);
}
private:
ofxVolumetrics volumetrics;
};
class pdImageSequencePlayer
{
public:
pdImageSequencePlayer(){};
void init(std::string prefix, int digits, std::string extension, int start)
{
imageSequencePlayer.init(prefix, digits, extension, start);
}
int getWidth()
{
return imageSequencePlayer.getWidth();
}
int getHeight()
{
return imageSequencePlayer.getHeight();
}
ofPixels_<unsigned char> getPixels()
{
return imageSequencePlayer.getPixels();
}
int getSequenceLength()
{
return imageSequencePlayer.getSequenceLength();
}
bool loadNextFrame()
{
return imageSequencePlayer.loadNextFrame();
}
bool loadPreviousFrame()
{
return imageSequencePlayer.loadPreviousFrame();
}
bool loadFrame(int n)
{
return imageSequencePlayer.loadFrame(n);
}
int getCurrentFrameNumber()
{
return imageSequencePlayer.getCurrentFrameNumber();
}
void setCurrentFrameNumber(int i)
{
imageSequencePlayer.setCurrentFrameNumber(i);
}
bool isInitialized()
{
return imageSequencePlayer.isInitialized();
}
private:
ofxImageSequencePlayer imageSequencePlayer;
};
Game of Life - new attempt
I simplified the shader a bit (got rid of a lot of conditional logic), also the cell state is set to the alpha channel (so the color of the living cells does not matter):
// fragment shader
#version 120
uniform sampler2D Tex0;
uniform vec2 resolution;
uniform float lCell_0;
uniform float lCell_1;
uniform float lCell_2;
uniform float lCell_3;
uniform float lCell_4;
uniform float lCell_5;
uniform float lCell_6;
uniform float lCell_7;
uniform float lCell_8;
uniform float dCell_0;
uniform float dCell_1;
uniform float dCell_2;
uniform float dCell_3;
uniform float dCell_4;
uniform float dCell_5;
uniform float dCell_6;
uniform float dCell_7;
uniform float dCell_8;
vec2 rule[9] = vec2[9](
vec2(lCell_0, dCell_0),
vec2(lCell_1, dCell_1),
vec2(lCell_2, dCell_2),
vec2(lCell_3, dCell_3),
vec2(lCell_4, dCell_4),
vec2(lCell_5, dCell_5),
vec2(lCell_6, dCell_6),
vec2(lCell_7, dCell_7),
vec2(lCell_8, dCell_8)
);
int get(int x, int y) {
return int(texture2D(Tex0, (gl_FragCoord.xy + vec2(x, y)) / resolution).a);
}
void main() {
int sum =
get(-1, -1) +
get(-1, 0) +
get(-1, 1) +
get( 0, -1) +
get( 0, 1) +
get( 1, -1) +
get( 1, 0) +
get( 1, 1);
vec2 r = rule[sum];
if (get(0, 0) == 1) {
gl_FragColor = vec4(0., 0., 0., r.x);
}
else {
gl_FragColor = vec4(0., 0., 0., r.y);
}
}
the current version is on my github page...
https://github.com/Jonathhhan/PDGameofLife-shaderVersion-
[SOLVED]video still remains after disconnecting [ofelia d] script
I've modified some abstractions created by @60hz for prototyping fast with ofelia (BTW, thanks a million for your abstractions they're great! And of course a million thanks to @cuinjune for creating Ofelia!). Things seem to work OK, but there's this happening. I have the following patch (I've changed the names from [gl.draw] to [ofelia.draw] for this and other abstractions)
[ofelia.draw]
|
[ofelia.cube]
and I see a white cube in the Ofelia window. Then I make the following connections:
[ofelia.draw]
|
[ofelia.movie]
|
[ofelia.cube]
and I get a video playing in the same cube. Till now everything is fine. But if I go back to the first patch, I don't get a white cube anymore but a still from the video file from [ofelia.movie]. Here are the scripts of the abstractions (almost identical to @60hz abstractions):
[ofelia.draw] (I'm returning ofColor because I want to have all abstractions output pointers and not bangs, maybe there's a better solution for this):
ofelia d draw$0;
local c = ofCanvas(this);
local args = c:getArgs();
local depth = true;
local screenwidth;
local screenheigh;
local widthHeigh;
;
function M.new();
if args[2] == nil then depth = true;
else;
M.depth(args[2]);
end;
if ofWindow.exists then;
screenwidth = ofGetWidth();
screenheight = ofGetHeight();
end;
end;
;
function M.screensize(list);
screenwidth, screenheight = list[1], list[2];
end;
;
function M.depth(string);
if string == "3d" then;
depth = true;
else;
depth = false;
end;
end;
function M.bang();
ofSetDepthTest(depth);
ofTranslate(screenwidth*0.5, screenheight*0.5);
return ofColor(255, 255, 255);
end;
[ofelia.movie]:
ofelia d -c17 videoplayer-$0;
local canvas = ofCanvas(this);
local args = canvas:getArgs();
local videoplayer = ofVideoPlayer();
local filename, start, loop = args[1], args[2], args[3];
local loaded = 0;
;
function M.new();
ofWindow.addListener("setup", this);
if args[1] == nil then print("No file found");
else M.open(filename);
end;
if args[2] == 1 then M.play();
end;
if args[3] == nil then loop = 0;
end;
end;
;
function M.free();
ofWindow.removeListener("setup", this);
end;
;
function M.setup();
M.open(filename);
end;
;
function M.open(string);
if ofWindow.exists then;
videoplayer:close();
videoplayer:load(string);
if (videoplayer:isLoaded()) then;
print("loaded " .. string);
videoplayer:update();
end;
end;
end;
function M.url(string);
if ofWindow.exists then;
videoplayer:close();
videoplayer:load(string);
if (videoplayer:isLoaded()) then;
print("loaded " .. string);
videoplayer:update();
end;
end;
end;
function M.play() videoplayer:play() end;
function M.stop() videoplayer:stop() end;
function M.pause() videoplayer:setPaused(true) end;
function M.speed(float) videoplayer:setSpeed(float) end;
function M.frame(float) videoplayer:setFrame(float) end;
function M.volume(float) videoplayer:setVolume(float) end;
function M.loop(float);
if float == 0 then videoplayer:setLoopState(OF_LOOP_NONE);
elseif float == 1 then videoplayer:setLoopState(OF_LOOP_NORMAL);
elseif float == 2 then videoplayer:setLoopState(OF_LOOP_PALINDROME);
end;
end;
function M.get()
return ofTable (videoplayer, videoplayer:isLoaded(), videoplayer:isPlaying(), videoplayer:getCurrentFrame(), videoplayer:getTotalNumFrames(), videoplayer:getWidth(), videoplayer:getHeight());
end;
;
function M.pointer(p);
videoplayer:update();
videoplayer:bind();
return videoplayer;
end;
function M.bang();
videoplayer:update();
videoplayer:bind();
return videoplayer;
end;
Inside [ofelia.movie] there's this patch:
[ofelia d movie_script] <- this is the ofelia object that loads the script above
|
[t a a]
| |
| [outlet]
|
[ofelia d videoplayer_unbind;] <- this is one object
[function M.pointer(p); ]
[p:unbind; ]
[end; ]
and this is [ofelia.cube]:
ofelia d $0-box;
local c = ofCanvas(this);
local args = c:getArgs();
local width, height, depth, resw, resh, resd, drawmode, strokeweight = args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8];
local position, orientation, scale = ofVec3f(0, 0, 0), ofVec3f(0, 0, 0), ofVec3f(1, 1, 1);
;
function M.new();
ofWindow.addListener("setup", this);
if args[1] == nil then width = 100 end;
if args[2] == nil then height = 100 end;
if args[3] == nil then depth = 100 end;
if args[4] == nil then resw = 5 end;
if args[5] == nil then resh = 5 end;
if args[6] == nil then resd = 5 end;
if args[7] == nil then drawmode = "fill" end;
if args[8] == nil then strokeweight = 1 end;
M.setup();
end;
;
function M.free();
ofWindow.removeListener("setup", this)
end;
;
function M.setup();
box$0 = ofBoxPrimitive();
end;
;
function M.resw(float) resw = float end;
function M.resh(float) resh = float end;
function M.resd(float) resd = float end;
function M.width(float) width = float end;
function M.height(float) height = float end;
function M.depth(float) depth = float end;
function M.draw(string) drawmode = string end;
function M.stroke(float) strokeweight = float end;
function M.position(list) position = ofVec3f(list[1], list[2], list[3]) end;
function M.orientation(list) orientation = ofVec3f(list[1], list[2], list[3]) end;
function M.scale(list) scale = ofVec3f(list[1], list[2], list[3]) end;
;
function M.pointer(p);
ofSetLineWidth(strokeweight);
box$0:setPosition (position:vec3());
box$0:setOrientation (orientation:vec3());
box$0:setScale (scale:vec3());
box$0:set(width, height, depth, math.abs(resw), math.abs(resh), math.abs(resd));
if drawmode == "fill" then box$0:drawFaces() end;
if drawmode == "point" then box$0:drawVertices() end;
if drawmode == "line" then box$0:drawWireframe() end;
return p;
end;
function M.bang();
ofSetLineWidth(strokeweight);
box$0:setPosition (position:vec3());
box$0:setOrientation (orientation:vec3());
box$0:setScale (scale:vec3());
box$0:set(width, height, depth, math.abs(resw), math.abs(resh), math.abs(resd));
if drawmode == "fill" then box$0:drawFaces() end;
if drawmode == "point" then box$0:drawVertices() end;
if drawmode == "line" then box$0:drawWireframe() end;
return anything;
end;
This is a bit too much information but I think it's necessary if anyone can help. There's probably stuff I'm ignorant of. @cuinjune any hints?
Scale something to something pretty and efficient
Because $f1
is already floating point, you gain nothing by multiplying by 1000000. That might be useful in the integer domain, but floating point numbers "shift" their full precision based on the magnitude, so it's a wasted operation.
Then, dividing by 1000000 is the same as multiplying by 0.000001. These are convenient numbers in decimal, but they are repeating fractions in binary floating point (like 1/7 = 0.142847142847...) so... you thought multiplying and dividing would give you extra precision but in fact, you've just introduced extra rounding error. This probably explains why it never reaches 1. (This is a very common misconception with floating point numbers. We tend to assume that a finite decimal should also be a finite float, but computer floats are binary. Any rational number where the denominator's prime factors are 2 and 5 will be finite in decimal, but a factor of 5 in the denominator will create an infinitely repeating binary representation, so 0.1 decimal is 1.1001100110011... x 2^(-4) in binary -- it's no more precise than 0.3333 in decimal or binary.)
hjh
Dynamic/programmatic generation of shapes
@whale-av I'm trying your [repeat] substitution but it seems not to work with gemlists.
[gemglxwindow]: Direct Rendering enabled!
translateXYZ: no method for 'symbol'
translateXYZ: no method for 'symbol'
translateXYZ: no method for 'symbol'
translateXYZ: no method for 'symbol'
translateXYZ: no method for 'symbol'
translateXYZ: no method for 'symbol'
translateXYZ: no method for 'symbol'
translateXYZ: no method for 'symbol'
consistency check failed: gpointer_copy
consistency check failed: gpointer_copy
inlet: expected 'float' but got 'pointer'
inlet: expected 'float' but got 'pointer'
translateXYZ: no method for 'symbol'
consistency check failed: gpointer_copy
inlet: expected 'float' but got 'pointer'
inlet: expected 'float' but got 'pointer'
translateXYZ: no method for 'symbol'
consistency check failed: gpointer_copy
inlet: expected 'float' but got 'pointer'
inlet: expected 'float' but got 'pointer'
translateXYZ: no method for 'symbol'
etc.
Am I doing it wrong? Or are gemlists "special" somehow?
hjh
Help with [key]/[keyup] inside [clone]
@4ZZ4 Sorry about the [t b f] and [notein].... it was a rush job.
[route float list symbol] is a bit special.
If you send [34 12( into [route 34 12] you will get a bang on the left outlet for 34 and one on the middle outlet for 12.
If you send it into [route float list] you will get 34 followed by 12 on the first outlet and 34 12 on the middle.
If you send [woof 23( into [route woof] you will get 23 on the left outlet.
If you send [woof 23( into [route symbol float] you will get woof on the left and 23 on the middle.
I think that is all correct. You can check it out in the [route] help file.
There is weird stuff going on that can be a pain and can be useful. Some of it is really weird.
But essentially [route] strips the tag of the atom with [route symbol list float] and sends on the atom but [route woof lala] or [route 34 35 36] strips that part and sends on what follows.
woof in the actual message is actually "symbol woof" in the internal message
34 35 36 is actually "list 34 35 36"
and 23 is actually "float 23" in the internal message.
The internal message is "tag something something something....... something]..... and float, symbol, pointer and list are the common tags.
http://puredata.info/docs/manuals/pd/x2.htm is good bedtime reading..... but don't worry about chapter 2.9 for a while.
David.