Jogamp
www/devmaster/lesson*.html: Fix header, title, navigation and links.
[joal-demos.git] / www / devmaster / lesson1.html
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2 <html>
3 <head>
4 <title>JOAL OpenAL Tutorials from DevMaster.net Lesson 1: Single Static Source</title>
5 <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" >
6 <link rel="stylesheet" type="text/css" href="general.css">
7 </head>
8 <body>
9 <a href="../index.html"><img src="../../../images/jogamp_symbols/website_final_blue_joal_346x70pel.png" alt="JOAL Symbol"></a><a href="http://www.openal.org"><img src="../openal_c.gif"></a>
10 <br>
11 <br>
12 OpenAL Tutorials from DevMaster.net. Reprinted with Permission.<br>
13 <br>
14
15 <table border="0" cellspacing="0" style="border-collapse: collapse" width="100%" cellpadding="0" id="AutoNumber1" height="12" bgcolor="#666699">
16     <tr>
17     <td width="47%" height="12" valign="middle"><p><b><font color="#FFFFFF">OpenAL 
18         Tutorials</font></b></p></td>
19       <td width="53%" height="12" align="right" valign="middle"><p align="right"><a href="http://devmaster.net/"><font color="#66FF99">DevMaster.net</font></a></p></td>
20     </tr>
21   </table>
22 <p align="left" class="title"><span class="title"><font size="5"><h1>Single Static 
23 Source</h1></font></span><font size="4">
24 <h2>Lesson 1</h2></font></p>
25
26 <p align="right" class="title"> <span class="author">Author: <a href="mailto:lightonthewater@hotmail.com"><font color="#888888">Jesse 
27   Maurais<br>
28   </font></a></span>Adapted for Java by: <a href="mailto:athomas@dev.java.net"><font color="#888888">Athomas 
29   Goldberg</font></a>
30 </p>
31   
32 <p><a href="../../../joal/www/jnlp-files/joal-lesson1.jnlp">Launch the Demo via Java Web Start</a></p>
33 <p align="justify">Welcome to the exciting world of OpenAL! OpenAL is still in 
34   a stage of growth, and even though there is an ever larger following to the 
35   API it still hasn't reached it's full potential. One of the big reasons for 
36   this is that there is still not yet hardware acceleration built in for specific 
37   cards. However, Creative Labs is a major contributor to the OpenAL project and 
38   also happens to be one of the largest soundcard manufacturers. So there is a 
39   promise of hardware accelerated features in the near future. OpenAL's only other 
40   major contributor, Loki, has gone the way of the dinosaur. So the future of 
41   OpenAL on Linux platforms is uncertain. You can still obtain the Linux binaries 
42   on some more obscure websites.</p>
43 <p align="justify">OpenAL has also not been seen in many major commercial products, 
44   which may have also hurt it's growth. As far as I know the only pc game to use 
45   OpenAL has been Metal Gear 2 (although recently I've discovered that Unreal 
46   2 does as well). The popular modeling program, Blender3D, was also known to 
47   use OpenAL for all it's audio playback. Aside from these however the only other 
48   OpenAL uses have been in the sdk examples and a few obscure tutorials on the 
49   internet.</p>
50 <p align="justify">But lets face it, OpenAL has a lot of potential. There are 
51   many other audio libraries that claim to work with the hardware on a lower level 
52   (and this may be true), but the designers of OpenAL did several things in it's 
53   design which make it a superior API. First of all they emulated the OpenGL API 
54   which is one of the best ever designed. The API style is flexible, so different 
55   coding methods and hardware implementations will take advantage of this. People 
56   who have had a lot of experience with OpenGL will be able to pick up OpenAL 
57   quite fast. OpenAL also has the advantage of creating 3D surround sound which 
58   a lot of other API's cannot boast. On top of all of that it also has the ability 
59   to extend itself into EAX and AC3 flawlessly. To my knowledge no other audio 
60   library has that capability.</p>
61 <p align="justify">If you still haven't found a reason here to use OpenAL then 
62   here's another. It's just cool. It's a nice looking API and will integrate well 
63   into your code. You will be able to do many cool sound effects with it. But 
64   before we do that we have to learn the basics.</p>
65
66 <p>So let's get coding!</p>
67 <pre class=code><font color="#0000FF">import</font> net.java.games.joal.*;
68 <font color="#0000FF">import</font> net.java.games.joal.util.*;
69 <font color="#0000FF">import</font> java.io.*;
70 <font color="#0000FF">import</font> java.nio.ByteBuffer;</pre>
71 <pre class=code><font color="#0000FF">public</font> <font color="#0000FF">class</font> SingleStaticSource {
72
73     <font color="#0000FF"><span class=codeComment>static</span></font><span class=codeComment> AL al = ALFactory.getAL();
74
75     <font color="#006600">// Buffers hold sound data.</font></span>
76 <font color="#0000FF">    static int</font>[] buffer = new <font color="#0000FF">int</font>[1];;
77
78 <span class=codeComment><font color="#006600">    // Sources are points emitting sound.</font></span>
79 <font color="#0000FF">    static int</font>[] source = <font color="#0000FF">new</font> <font color="#0000FF">int</font>[1];
80 </pre>
81 <p align="justify">Those familiar with OpenGL know that it uses &quot;texture 
82   objects&quot; (or &quot;texture names&quot;) to handle textures used by a program. 
83   OpenAL does a similar thing with audio samples. There are essentially 3 kinds 
84   of objects in OpenAL. A buffer which stores all the information about how a 
85   sound should be played and the sound data itself, and a source which is a point 
86   in space that emits a sound. It's important to understand that a source is not 
87   itself an audio sample. A source only plays back sound data from a buffer bound 
88   to it. The source is also given special properties like position and velocity. 
89 </p>
90 <p align="justify">The third object which I have not mentioned yet is the listener. 
91   There is only one listener which represents where 'you' are, the user. The listener 
92   properties along with the source properties determine how the audio sample will 
93   be heard. For example their relative positions will determine the intensity 
94   of the sound.</p>
95
96 <pre class=code>        
97 <span class=codeComment><font color="#006600">    // Position of the source sound.</font></span>
98 <font color="#0000FF"><span class=codeComment><font color="#006600">    </font></span>static float</font>[] sourcePos = { 0.0f, 0.0f, 0.0f };
99 <font color="#006600">
100 <span class=codeComment><font color="#006600">    </font>// Velocity of the source sound.</span></font>
101 <font color="#0000FF"><span class=codeComment><font color="#006600">    </font></span>static float</font>[] sourceVel = { 0.0f, 0.0f, 0.0f };
102
103 <span class=codeComment><font color="#006600">    </font><font color="#006600">// Position of the listener.</font></span>
104 <span class=codeComment><font color="#006600">    </font></span><font color="#0000FF">static float</font>[] listenerPos = { 0.0f, 0.0f, 0.0f };
105
106 <span class=codeComment><font color="#006600">    </font><font color="#006600">// Velocity of the listener.</font></span>
107 <span class=codeComment><font color="#006600">    </font></span><font color="#0000FF">static float</font>[] listenerVel = { 0.0f, 0.0f, 0.0f };
108
109 <span class=codeComment><font color="#006600">    </font><font color="#006600">// Orientation of the listener. (first 3 elements are "at", second 3 are "up")</font></span>
110 <span class=codeComment><font color="#006600">    </font></span><font color="#0000FF">static float</font>[] listenerOri = { 0.0f, 0.0f, -1.0f,  0.0f, 1.0f, 0.0f };
111 </pre>
112
113 <p align="justify">In the above code we specify the position and velocity of the 
114   source and listener objects. These arrays are vector based Cartesian coordinates. 
115   You could easily build a structure or class to do the same thing. In this example 
116   I used arrays for simplicity.</p>
117 <p>Here we will create a function that loads all of our sound data from a file. 
118 </p>
119 <pre class=code><span class=codeComment><font color="#006600">    </font></span><font color="#0000FF">static int</font> LoadALData() {
120
121 <span class=codeComment><font color="#006600">    </font><font color="#006600">    </font><font color="#006600">// variables to load into</font>
122    
123 <font color="#006600">    </font><font color="#006600">    </font><font color="#0000FF">int</font>[] format = <font color="#0000FF">new int</font>[1];<br><font color="#006600">    </font><font color="#006600">    </font><font color="#0000FF">int</font>[] size = <font color="#0000FF">new int</font>[1];<br><font color="#006600">    </font><font color="#006600">    </font>ByteBuffer[] data = <font color="#0000FF">new</font> ByteBuffer[1];<br><font color="#006600">    </font><font color="#006600">    </font><font color="#0000FF">int</font>[] freq = new <font color="#0000FF">int</font>[1];
124 <font color="#006600">    </font><font color="#006600">    </font><font color="#0000FF">int</font>[] loop = new <font color="#0000FF">int</font>[1];<br></span>
125 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><font color="#006600">// Load wav data into a buffer.</font>
126 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>al.alGenBuffers(1, buffer);
127 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeKeyword>if</span> (al.alGetError() != AL.AL_NO_ERROR)
128 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeKeyword>return</span> AL.AL_FALSE;
129
130 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>ALut.alutLoadWAVFile(&quot;wavdata/FancyPants.wav&quot;, format, data, size, freq, loop);
131 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>al.alBufferData(buffer[0], format[0], data[0], size[0], freq[0]);
132 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>ALut.alutUnloadWAV(format[0],data[0],size[0],freq[0]); 
133 </pre>
134 <p align="justify">The function 'alGenBuffers' will create the buffer objects 
135   and store them in the variable we passed it. It's important to do an error check 
136   to make sure everything went smoothly. There may be a case in which OpenAL could 
137   not generate a buffer object due to a lack of memory. In this case it would 
138   set the error bit.</p>
139 <p align="justify">The ALut is very helpful here. It opens up the file for us 
140   and gives us all the information we need to create the buffer. And after we 
141   have attached all this data to the buffer it will help use dispose of the data. 
142   It all works in a clean and efficient manner.</p>
143 <pre>
144
145 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><font color="#006600">// Bind buffer with a source.</font>
146 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>al.alGenSources(1, source);
147
148 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeKeyword>if</span> (al.alGetError() != AL.AL_NO_ERROR)
149         <span class=codeComment><font color="#006600">    </font></span><span class=codeKeyword>return</span> AL.AL_FALSE;
150
151     <span class=codeComment><font color="#006600">    </font></span>al.alSourcei (source[0], AL.AL_BUFFER,   buffer[0]   );
152 <span class=codeComment><font color="#006600">    </font></span>    al.alSourcef (source[0], AL.AL_PITCH,    1.0f     );
153 <span class=codeComment><font color="#006600">    </font></span>    al.alSourcef (source[0], AL.AL_GAIN,     1.0f     );
154 <span class=codeComment><font color="#006600">    </font></span>    al.alSourcefv(source[0], AL.AL_POSITION, sourcePos);
155 <span class=codeComment><font color="#006600">    </font></span>    al.alSourcefv(source[0], AL.AL_VELOCITY, sourceVel);
156 <span class=codeComment><font color="#006600">    </font></span>    al.alSourcei (source[0], AL.AL_LOOPING,  loop[0]     );</pre>
157 <p align="justify">We generate a source object in the same manner we generated 
158   the buffer object. Then we define the source properties that it will use when 
159   it's in playback. The most important of these properties is the buffer it should 
160   use. This tells the source which audio sample to playback. In this case we only 
161   have one so we bind it. We also tell the source it's position and velocity which 
162   we defined earlier.</p>
163 <p align="justify">One more thing on 'alGenBuffers' and 'alGenSources'. In some 
164   example code I have seen these functions will return an integer value for the 
165   number of buffers/sources created. I suppose this was meant as an error checking 
166   feature that was left out in a later version. If you see this done in other 
167   code don't use it yourself. If you want to do this check, use 'alGetError' instead 
168   (like we have done above).</p>
169 <pre class=code>
170
171 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><font color="#006600">// Do another error check and return.</font>
172     <span class=codeComment><font color="#006600">    </font></span><font color="#0000FF">if</font>(al.alGetError() == AL.AL_NO_ERROR)
173 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><font color="#0000FF">return</font> AL.AL_TRUE;
174
175 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeKeyword><font color="#0000FF">return</font></span> AL.AL_FALSE;
176 <span class=codeComment><font color="#006600">    </font></span>}
177 </pre>
178 <p>To end the function we just do one more check to make sure all is well, then 
179 we return success.</p>
180 <pre class=code><font color="#0000FF"><span class=codeComment><font color="#006600">    </font></span>static <span class=codeKeyword>void</span> </font>setListenerValues() {
181 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>al.alListenerfv(AL.AL_POSITION, listenerPos);
182 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>al.alListenerfv(AL.AL_VELOCITY,    listenerVel);
183 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>al.alListenerfv(AL.AL_ORIENTATION, listenerOri);
184 <span class=codeComment><font color="#006600">    </font></span>}
185 </pre>
186 <p>We created this function to update the listener properties.</p>
187 <pre class=code><font color="#0000FF"><span class=codeComment><font color="#006600">    </font></span>static <span class=codeKeyword>void</span> </font>killALData() {
188 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>al.alDeleteBuffers(1, buffer);
189 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>al.alDeleteSources(1, source);
190 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>Alut.alutExit();
191 <span class=codeComment><font color="#006600">    </font></span>}
192 </pre>
193 <p>This will be our shutdown procedure. It is necessary to call this to release 
194 all the memory and audio devices that our program may be using.</p>
195 <pre class=code><font color="#0000FF"><span class=codeComment><font color="#006600">    </font></span>public static void main</font>(<span class=codeKeyword>String[] args</span>) {
196 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">// Initialize OpenAL and clear the error bit.</font></span>
197 <span class=codeComment>
198 <font color="#006600">    </font><font color="#006600">    </font>ALut.alutInit();</span>
199 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>al.alGetError();
200
201 </pre>
202 <p align="justify">The function 'alutInit' will setup everything that the Alc 
203   needs to do for us. Basically Alut creates a single OpenAL context through Alc 
204   and sets it to current. On the Windows platform it initializes DirectSound. 
205   We also do an initial call to the error function to clear it. Every time we 
206   call 'glGetError' it will reset itself to 'AL_NO_ERROR'.</p>
207 <pre class=code>
208
209 <span class=codeComment><font color="#006600">    </font><font color="#006600">    </font><font color="#006600">// Load the wav data.</font></span>
210 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeKeyword><font color="#0000FF">if</font></span> (loadALData() == AL.AL_FALSE)
211 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeKeyword><font color="#0000FF">return</font></span> -1;
212
213 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>setListenerValues();
214
215 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">// Setup an exit procedure.</font></span>
216
217 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>Runtime runtime = Runtime.getRuntime();
218 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>runtime.addShutdownHook(
219 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><font color="#0000FF">new</font> Thread(
220 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><font color="#0000FF">new</font> Runnable() {
221 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><font color="#0000FF">public void</font> run() {
222 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>killAllData();
223 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>}
224 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>}
225 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>)
226 <span class=codeComment><font color="#006600">    </font></span><span class=codeComment><font color="#006600">    </font></span>);
227 </pre>
228 <p align="justify">We will check to see if the wav files loaded correctly. If not we must exit the program. Then we update the listener values,
229 and finally we set our exit procedure.</p>
230 <pre class=code><font color="#0000FF">        char</font>[] c = new <font color="#0000FF">char</font>[1];
231 <font color="#0000FF">    </font><font color="#0000FF">    while</font>(c[0] != 'q') {  
232 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">try</font> {
233 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font>BufferedReader buf =
234 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">new</font> BufferedReader(<font color="#0000FF">new</font> InputStreamReader(System.in));
235 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font>System.out.println(&quot;Press a key and hit ENTER: &quot; +
236 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">   </font>&quot;'p' to play, 's' to stop, 'h' to pause and 'q' to quit&quot;);
237 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font>buf.read(c);
238 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">switch</font>(c[0]) {
239 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">case</font> 'p':
240 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#006600">// Pressing 'p' will begin playing the sample.</font>
241 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font>al.alSourcePlay(source[0]);
242 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">break</font>;
243 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">case</font> 's':
244 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#006600">// Pressing 's' will stop the sample from playing.</font>
245 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font>alSourceStop(source[0]);
246 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">break</font>;
247 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">case</font> 'h':
248 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#006600">// Pressing 'n' will pause (hold) the sample.</font>
249 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font>alSourcePause(source[0]);
250 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">break</font>;
251 <font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font><font color="#0000FF">    </font>}
252 <font color="#0000FF">    </font><font color="#0000FF">    </font>} <font color="#0000FF">catch</font> (IOException e) {
253                         System.exit(1);
254 <font color="#0000FF">    </font><font color="#0000FF">    </font>}
255 <font color="#0000FF">    </font>}
256 }
257
258 </pre>
259 <p align="justify">This is the interesting part of the tutorial. It's a very basic 
260   loop that lets us control the playback of the audio sample. Pressing 'p' will 
261   replay the sample, pressing 's' will stop the sample, and pressing 'h' will 
262   pause the sample. Pressing 'q' will exit the program.</p>
263 <p align="justify">Well there it is. Your first delve into OpenAL. I hope it was 
264   made simple enough for you. It may have been a little too simple for the 1337 
265   h4X0r, but we all got to start somewhere. Things will get more advanced as we 
266   go along.</p>
267 <table border="0" cellspacing="1" style="border-collapse: collapse" width="100%" id="AutoNumber2" bgcolor="#666699">
268   <tr> 
269     <td width="40%"> <p dir="ltr"><font color="#FFFFFF" size="2">© 2003 DevMaster.net. 
270         All rights reserved.</font></td>
271     <td width="60%"> <p align="right" dir="ltr"><font size="2"><a href="mailto:webmaster@devmaster.net"> 
272         <font color="#FFFFFF">Contact us</font></a><font color="#FFFFFF"> if you 
273         want to write for us or for any comments, suggestions, or feedback.</font></font></td>
274   </tr>
275 </table>
276 <p>&nbsp;</p>
277 </body>
278 </html>
http://JogAmp.org git info: FAQ, tutorial and man pages.