This is how a wave header gets written to store LaserBoy data.
Code:
#define LASERBOY_WAVE_VERSION "LaserBoy07132008"
//----------------------------------------------------------------------------
#define EZ_WAVE_POSITIVE 1
#define EZ_WAVE_END_OF_FRAME 2
#define EZ_WAVE_UNIQUE_FRAME 4
#define EZ_WAVE_UNIQUE_POINT 8
#define EZ_WAVE_OFFSETS 16
#define EZ_WAVE_OPTIMIZED 32
//----------------------------------------------------------------------------
//############################################################################
void ez_wave_header::to_ofstream_wave(ofstream& out, bool with_laserboy_stuff) const
{
short audio_format = 1,
block_align = num_channels * (bits_per_sample / 8);
int i,
sub_chunk_1_size = 16, // standard wave Subchunk1Size
sub_chunk_2_size = num_samples * num_channels * (bits_per_sample / 8),
byte_rate = sample_rate * num_channels * (bits_per_sample / 8),
chunk_size = 20 + sub_chunk_1_size + sub_chunk_2_size;
//------------------------------------------------------------------------
if(with_laserboy_stuff)
sub_chunk_1_size += ( 16 // sizeof "LaserBoy06062008"
+ 4 // sizeof (int)ez_wave_mode
+ ( (ez_wave_mode & EZ_WAVE_OFFSETS)
? (num_channels * sizeof(int))
: (0)
)
+ ( (ez_wave_mode & EZ_WAVE_OPTIMIZED)
? (6 * sizeof(int)) // six 32bit numbers
: (0)
)
);
//------------------------------------------------------------------------
out.put('R');
out.put('I');
out.put('F');
out.put('F');
out.put((char) (chunk_size & 0x000000ff) );
out.put((char)((chunk_size & 0x0000ff00) >> 8 ));
out.put((char)((chunk_size & 0x00ff0000) >> 16));
out.put((char)((chunk_size & 0xff000000) >> 24));
out.put('W');
out.put('A');
out.put('V');
out.put('E');
out.put('f');
out.put('m');
out.put('t');
out.put(' ');
out.put((char) (sub_chunk_1_size & 0x000000ff) );
out.put((char)((sub_chunk_1_size & 0x0000ff00) >> 8 ));
out.put((char)((sub_chunk_1_size & 0x00ff0000) >> 16));
out.put((char)((sub_chunk_1_size & 0xff000000) >> 24));
out.put((char) (audio_format & 0x00ff) );
out.put((char)((audio_format & 0xff00) >> 8)); // 2
out.put((char) (num_channels & 0x00ff) );
out.put((char)((num_channels & 0xff00) >> 8)); // 4
out.put((char) (sample_rate & 0x000000ff) );
out.put((char)((sample_rate & 0x0000ff00) >> 8 ));
out.put((char)((sample_rate & 0x00ff0000) >> 16));
out.put((char)((sample_rate & 0xff000000) >> 24)); // 8
out.put((char) (byte_rate & 0x000000ff) );
out.put((char)((byte_rate & 0x0000ff00) >> 8 ));
out.put((char)((byte_rate & 0x00ff0000) >> 16));
out.put((char)((byte_rate & 0xff000000) >> 24)); // 12
out.put((char) (block_align & 0x00ff) );
out.put((char)((block_align & 0xff00) >> 8)); // 14
out.put((char) (bits_per_sample & 0x00ff) );
out.put((char)((bits_per_sample & 0xff00) >> 8)); // 16
//----------------------------------------------------------------------------
// extra structures added for LaserBoy !!!
//----------------------------------------------------------------------------
if(with_laserboy_stuff)
{
out.put(header_version[ 0]); // L
out.put(header_version[ 1]); // a
out.put(header_version[ 2]); // s
out.put(header_version[ 3]); // e
out.put(header_version[ 4]); // r
out.put(header_version[ 5]); // B
out.put(header_version[ 6]); // o
out.put(header_version[ 7]); // y
out.put(header_version[ 8]); // M
out.put(header_version[ 9]); // M
out.put(header_version[10]); // D
out.put(header_version[11]); // D
out.put(header_version[12]); // C
out.put(header_version[13]); // C
out.put(header_version[14]); // Y
out.put(header_version[15]); // Y
//--------------------------------------------------------------------
out.put((char) (ez_wave_mode & 0x000000ff) );
out.put((char)((ez_wave_mode & 0x0000ff00) >> 8 ));
out.put((char)((ez_wave_mode & 0x00ff0000) >> 16));
out.put((char)((ez_wave_mode & 0xff000000) >> 24)); // 36
//--------------------------------------------------------------------
if(ez_wave_mode & EZ_WAVE_OFFSETS)
for(i = 0; i < num_channels; i++)
{
out.put((char)( offset[i] & 0x000000ff) );
out.put((char)((offset[i] & 0x0000ff00) >> 8 ));
out.put((char)((offset[i] & 0x00ff0000) >> 16));
out.put((char)((offset[i] & 0xff000000) >> 24)); // 36 + 4 * num_channels
}
//--------------------------------------------------------------------
if(ez_wave_mode & EZ_WAVE_OPTIMIZED)
{
out.put((char) (parms.lit_dwell_overhang & 0x000000ff) ); // int
out.put((char)((parms.lit_dwell_overhang & 0x0000ff00) >> 8 ));
out.put((char)((parms.lit_dwell_overhang & 0x00ff0000) >> 16));
out.put((char)((parms.lit_dwell_overhang & 0xff000000) >> 24));
out.put((char)(((int)(parms.distance_delta_max) & 0xff000000) >> 24)); // float
out.put((char)(((int)(parms.distance_delta_max) & 0x00ff0000) >> 16));
out.put((char)(((int)(parms.distance_delta_max) & 0x0000ff00) >> 8 ));
out.put((char) ((int)(parms.distance_delta_max) & 0x000000ff) );
out.put((char)(((int)(parms.longest_dwell_microsec) & 0xff000000) >> 24)); // float
out.put((char)(((int)(parms.longest_dwell_microsec) & 0x00ff0000) >> 16));
out.put((char)(((int)(parms.longest_dwell_microsec) & 0x0000ff00) >> 8 ));
out.put((char) ((int)(parms.longest_dwell_microsec) & 0x000000ff) );
out.put((char)(((int)(parms.insignificant_distance) & 0xff000000) >> 24)); // float
out.put((char)(((int)(parms.insignificant_distance) & 0x00ff0000) >> 16));
out.put((char)(((int)(parms.insignificant_distance) & 0x0000ff00) >> 8 ));
out.put((char) ((int)(parms.insignificant_distance) & 0x000000ff) );
out.put((char)(((int)(parms.zero_angle_tolerance) & 0xff000000) >> 24)); // float
out.put((char)(((int)(parms.zero_angle_tolerance) & 0x00ff0000) >> 16));
out.put((char)(((int)(parms.zero_angle_tolerance) & 0x0000ff00) >> 8 ));
out.put((char) ((int)(parms.zero_angle_tolerance) & 0x000000ff) );
out.put((char)(((int)(parms.frames_per_second) & 0xff000000) >> 24)); // float
out.put((char)(((int)(parms.frames_per_second) & 0x00ff0000) >> 16));
out.put((char)(((int)(parms.frames_per_second) & 0x0000ff00) >> 8 ));
out.put((char) ((int)(parms.frames_per_second) & 0x000000ff) );
}
} // end if(with_laserboy_stuff)
//------------------------------------------------------------------------
out.put('d');
out.put('a');
out.put('t');
out.put('a');
out.put((char) (sub_chunk_2_size & 0x000000ff) );
out.put((char)((sub_chunk_2_size & 0x0000ff00) >> 8 ));
out.put((char)((sub_chunk_2_size & 0x00ff0000) >> 16));
out.put((char)((sub_chunk_2_size & 0xff000000) >> 24));
return;
}
//############################################################################
It has been added to over the years, mostly in just the past few months. I have plans to add more to it in the near future.
The reader for LaserBoy waves is a bit more complicated, because it knows how to read many generations of LaserBoy written waves.
Writing ints and floats looks weird in this example, but it guarantees that I get every byte in the right order!
The value of ez_wave_mode is the bitwize OR of the settings:
Code:
#define EZ_WAVE_POSITIVE 1
#define EZ_WAVE_END_OF_FRAME 2
#define EZ_WAVE_UNIQUE_FRAME 4
#define EZ_WAVE_UNIQUE_POINT 8
#define EZ_WAVE_OFFSETS 16
#define EZ_WAVE_OPTIMIZED 32
Code:
//-----------------------------------------------------------
// Standard Microsoft RIFF wave file header stuff...
//-----------------------------------------------------------
"RIFF" 32-bits unterminated string
chunk_size 32-bits int
"WAVE" 32-bits unterminated string
"fmt " 32-bits unterminated string
sub_chunk_1_size 32-bits int
audio_format 16-bits short
num_channels 16-bits short
sample_rate 32-bits int
byte_rate 32-bits int
block_align 16-bits short
bits_per_sample 16-bits short
//-----------------------------------------------------------
// extra structures added for LaserBoy !!!
//-----------------------------------------------------------
"LaserBoy" 64-bits unterminated string
"07132008" 64-bits unterminated string
//-----------------------------------------------------------
ez_wave_mode 32-bits int
//-----------------------------------------------------------
//-----------------------------------------------------------
if(ez_wave_mode & EZ_WAVE_OFFSETS)
offsets num_channels * 32-bits int
//-----------------------------------------------------------
//-----------------------------------------------------------
if(ez_wave_mode & EZ_WAVE_OPTIMIZED)
lit_dwell_overhang 32-bits int
distance_delta_max 32-bits float
longest_dwell_microsec 32-bits float
insignificant_distance 32-bits float
zero_angle_tolerance 32-bits float
frames_per_second 32-bits float
//-----------------------------------------------------------
//-----------------------------------------------------------
// Standard Microsoft RIFF wave file header stuff...
//-----------------------------------------------------------
"data" 32-bits unterminated string
sub_chunk_2_size 32-bits int
//-----------------------------------------------------------
Above is a list of the data that gets written to a LaserBoy wave header. When reading this header back, the first thing to look for is the LaserBoy wave version number. This is followed by a 32 bit number that is a set of Boolean flags for things that may (or may not) follow after that. When writing the header, it is necessary to know the cumulative size of these individual parts. In the code example above this list, the value of sub_chunk_1_size is calculated.
If this wave has been sample shifted to align the scanners and the color mod channels, the a set of integer offsets will follow, right after the value of ez_wave_mode.
If this wave has been optimized for streaming directly to the multi-channel DAC for display, there will be a block of values that were used in the optimization process. These values are informational and can be seen in the LaserBoy development environment. If you get a wave from someone else and it was optimized for their system, you might want to know what values were used.
Once a LaserBoy wave has been opened in LaserBoy, it can be re-optimized with a different set of parameters, that might be better for the performance of your laser system.
It is also very important to note that even though this is a wave file, that does not mean that it MUST be played through a DAC in any kind of "real-time" situation. That is to say, that the wave file format is much more useful and representative than that! It can be just a nice arrangment of associated sample values, with no time synchronization at all. It might just be a set of associated values in natural order ~ and still be the best choice of file format for streaming; especially to inteligent devices!
I have found that there is a whole universe of "implicit" information that exists "in between" the samples. The relationship that exists from one sample to the next and the overall order of all of the samples has everything to do with the useful state of these things. The real value of LaserBoy is to be able to take up all of this information and not only make it mutable and dynamic in computer memory, but to fully understand and exploit the point to point relationship that exists in this data.
The next thing that gets written, right after this header is the formatted data.
There are some variations to what this might be, depending on the settings that exist in this header, but the general rule is that each sample is a set of 6 signed short integers (16 bit), in the order X, Y, R, G, B, I.
When LaserBoy makes the wave, it is made with no time correction between the scanner and the color mod channels. So a chunk of offsets is not saved. Every sample is considered to be a single data element and each value within that multi-channel sample is to be considered relevant to that element.
The way LaserBoy does the time correction is to read every sample and re-write them all to disk. In the process, the individual tracks are offset from each other such that the X & Y information will appear to be ahead (in time) of the color mod information. If the offsets are {0, 0, -7, -7, -7, -7}, then the scanner signals will come out of the DAC seven samples before the corresponding color signals.
Since this offset information is stored in the header, it can be un-done and re-done for different timing situations.
If the value of ez_wave_mode & EZ_WAVE_POSITIVE is true, the data will be written assuming that a positive 16 bit short integer will come out the other side of the DAC as a positive voltage. If the value of ez_wave_mode & EZ_WAVE_POSITIVE is false, every individual 16 bit short integer found in every multi-channel sample will be negated.
Since the color mod channels are really 8 bit unsigned values that get pushed up 7 bits to make them 16 bit positive signed values, it is possible to use the LSB of any of these tracks to drop markers into the wave data. This has no effect on the quality of the playback of the wave through the DAC. The first 7 bits of any of these values will always be binary 0000000. The difference between binary 10000000 and binary 10000001, divided by decimal 128 is nothing.
So we drop a 1 into the LSB of the red channel for the end of every frame.
If this wave is an animation of a set of frames and it was made to emulate a certain frames-per-second play rate, then it is likely that each frame might be scanned more than once, consecutively.
So we drop a 1 into the LSB of the green channel to designate that this one frame is unique. Duplicate scans that follow are not unique.
The samples that get the marks are considered to be in the non-sample-shifted state. The LSB 1 in the red marks the end of the frame scan and that is the same sample to look for the LSB in green.
There is a lot more to it than this, but I would like to help get some people going who want to try writing to wave that is LaserBoy compatible.
Please feel free to ask a lot of questions and I will edit this post as needed.
James.