Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 17 of 17

Thread: AudioSDR - A single Audio block SDR (software defined radio) processor/demodulator

  1. #1
    Senior Member
    Join Date
    May 2016
    Posts
    218

    AudioSDR - A single Audio block SDR (software defined radio) processor/demodulator

    I'm pleased to announce that I have finally uploaded my AudioSDR project to Github here. I've had it working for many months now, but wanted to give it a good workout before releasing it.

    AudioSDR is a single-block, Teensy Audio Library compatible, signal processor/demodulator for use in direct-conversion software-defined-radio (SDR) systems. It provides high quality demodulation for LSB, SSB, AM (using envelope detection or SAM), CW (using both LSB and USB modes), and WSPR. It is designed to work at the standard Teensy Audio Library sampling rate of 44.1kHz, uses the standard 128 sample block size, and uses the Teensy Audio Board's I2S inputs and outputs.
    In its most basic form it requires no other Audio objects to produce a minimal functional system.

    The AudioSDR block has the following features:
    - It uses floating point (f32) processing throughout the signal processing chain.
    - It is a dual-conversion receiver with an intermediate frequency (IF) at approximately 7kHz, and has mode dependent IF filters.
    - It contains demodulators for single-sideband (USB and LSB), AM/SAM), and has a WSPR (Weak-Signal-Propagation-Reporter) mode.
    - It includes an impulse noise-blanker with adjustable threshold.
    - It has AGC with variable attack, release and hang times.
    - It contains an ALS (adaptive-least-squares) automatic notch/peak lter.
    - It (currently) has nine selectable audio output band-pass filters.
    - Because it uses floating point it is only suitable for use with the Teensy 3.6 (and hopefully, the T4)

    AudioSDR is not a complete SDR receiver: it requires a quadrature (IQ) rf front-end such as a Soft-Rock or QRP Labs Receiver Module. Nor does it (deliberately) address the issue of a front panel with switches/tuning-control/display etc. An extensive set of public functions is provided to control all aspects of the operation. The package also includes a couple of useful audio-blocks, one of which will detect and correct the random Teensy I2S input alignment bug. A detailed pdf document is provided. and some simple stand alone demos are provided. (I've also included some short audio clips)

    If anybody wants to try it, I'll be delighted to work with you. It is - and always will be - a work-in-progress, and I invite collaboration with anyone who is interested.

    I am working on implementing AudioSDR on the T4 as we speak. I am starting by implementing the stand-alone (no controls) WSPR receiver that is included in the package.

    Derek

  2. #2
    Senior Member DD4WH's Avatar
    Join Date
    Oct 2015
    Location
    Central Europe
    Posts
    445
    Hi Derek,

    I am really impressed, that seems a really nicely structured SDR layout! I will soon make an attempt to try it out!

    However, when compiling, "Streaming.h" is missing. Where can I find that?

    All the best,

    Frank DD4WH

  3. #3
    Senior Member
    Join Date
    May 2016
    Posts
    218
    Hi Frank,
    Super to hear from you. I'll be really, really interested to hear what you think. Please let me know of any problems, criticisms etc (I'll PM my email to you). Nobody else has tried audioSDR before I posted it here, and my rf environment is sooo bad, with high voltage lines running down the street right opposite, that it's difficult to judge how well it is working.

    I apologize about "Streaming.h", I was only using it for debugging. You can comment it out in the includes for AudioSDR.h. I'll update gitHub tonight.

    That said, I find Streaming to be a useful thing to have around. It simply replaces Serial.print() and Serial.println() and allows you to easily print multiple things on a line, with a syntax :

    Serial << "GPS #" << gpsno << " date: " << day << "-" << month << "-" << year << endl;

  4. #4
    Senior Member
    Join Date
    May 2016
    Posts
    218
    I uploaded some small mods to GitHub and called it Ver, 1.01. There are no functional differences:
    1: Removed the #include "Streaming.h" from AudioSDR.h (which was a hangover from debugging).
    2: Added #include "core_pins.h" in all .h files in AudioSDRlib to prevent compilation errors for Teensy 4.0.

    I have found that none of my Audio library compatible classes will compile for T4 without including core_pins.h before the #include AudioStream,h. I have fixed the problem for myself by modifying AudioStream.h to include core_pins.h, but it does no harm (I hope) to include it in each library object. We are in uncharted waters here...

  5. #5
    Senior Member
    Join Date
    May 2016
    Posts
    218

    AudioSDR is fully fuctional with Teensy 4.0!

    I have just finished wiring a new board to allow the T4 + Audio Board to work with the QRP Labs Receiver Module. I nearly fell off my chair when I compiled the BareBones WSPR receiver (that's included in the AudioSDR distro) on the very first attempt, loaded it, plugged it to WJST-X on the PC, and bingo - there was a stream of 40m WSPR on the waterfall. All I had to do was calibrate the SI5351 oscillator. I'll post photos tomorrow.

    It's a bit too late to bring in the European stations tonight, but I'm letting it "soak" overnight and I'll take a look at how well it did in the morning...

    The next job is a complete redesign/rebuild of all of my main SDR hardware in order to accommodate the Teensy 4. I'm starting to think about using two T4's - one strictly for signal processing, and a separate one for the panadapter/display and all the front panel interaction, but that might be overkill. I've had a RA8875 5" display sitting around for 3+ years and I'll probably use it on the next iteration.

  6. #6
    Junior Member
    Join Date
    May 2017
    Location
    Spokane Valley, WA
    Posts
    10
    Congrats, Derek! Excellent work.
    I've wondered about using IIR filters for AM/CW/SSB filtering, but never got around to finding out how they'd work.

    Doing FFT convolution is a strain on the Teesny 3.6, even with modest-length impulse responses. I use 2048 taps @ 96 kHz in the package for the Multus Proficio, but that's not a big deal on a PC.
    I'm interested in doing an updated embedded package on an ARM for physical portability and to get the latency down.

    Exciting stuff!

    73,
    Jim N7CXI

  7. #7
    Senior Member
    Join Date
    May 2016
    Posts
    218
    Thanks Jim,
    I spent a lot of time working looping at frequency domain processing and FFT convolution for use in SDR, but ended up asking myself: why am I doing this? It's interesting - I spent 40+ years teaching DSP, and I would tout the efficiency of the FFT method, but when I came to using it on a MCU (particularly in the SDR context) I found that, while fine in principle, it was full of problems:

    It's only good for linear signal processing, and at the slightest hint of nonlinear processing you must immediately come back to the time domain. FFT filtering in the IF band-pass filters was fine, but when using a Hartley (phase-shift) SSB/CW demod, and/or envelope detection for AM, I had to come back to the time domain. For the AM case it's obviously a nonlinear process, for the SSB/CW the problem was with the Hilbert transform phase shift.

    We all know that there is no such thing as an exact numerical Hilbert transform. There is a well known frequency domain approximation, in fact MATLAB's hilbert() function uses it. It simply zonks (that's a technical term ) the negative frequency components, with a simple transition at 0 and the Nyquist frequency. So I tried it in AudioSDR, and it worked - except it introduced audible transients at the end of each data block. So I went back to MATLAB, and sure enough it also had exactly the same transients. I struggled with it for a few days but in the end went back to a time domain conventional FIR Hilbert transformer. (It's interesting that if you design the FIR coefficients properly you can reduce the number of required multiplications by a factor of four.)

    I should say that I am still looking at a frequency domain implementation of the Weaver demodulator (since it does not require a Hilbert transform)

    I think that AudioSDR is stretching the T3.6 to very close to its limits. I had to deiberately truncate/simplify some of the operations to keep the block execution time reasonable. You can't go all the way to the 2.9 msec update timing because you have to allow time for processing with your loop() function.

    Re the use of IIR filters, that really was just to minimize the computational load. I used elliptical filters because they have the sharpest transition for a given filter order. I generally set the stop-band rejection at -60 dB. As far as I can tell they work well.

    We have a whole new world with the T4, and I hope to take a fresh look at all aspects of the design, including new features.

  8. #8
    Senior Member DD4WH's Avatar
    Join Date
    Oct 2015
    Location
    Central Europe
    Posts
    445
    Hi Derek,

    thats very interesting stuff! For me as a non-engineer, there are a few points that are not easily understandable:

    1.) SSB: Where is the Hilbert transform in Fast convolution filtering in the frequency domain when demodulating SSB? What I do in an overlap-and-save scheme is: FFT - complex multiply with filter mask of complex FIR coefficients - inverse FFT. After that you do not need any demodulation process or Hilbert filter, you already have "demodulated" SSB to listen to.

    2.) I do a similar thing for AM: FFT - complex multiply - inverse FFT, then I simply take sqrt(I*I + Q*Q) [in the time domain]. For me there is no obvious Hilbert transform in those demodulation steps.

    But maybe I am missing something here or we are talking about different things, for example I use complex FFTs and filter I & Q simultaneously in one FFT / inverse FFT step. I also use complex FIR coefficients and windowing in the coefficient calculation, so it may differ from what people usually do.

    3.) I recently coded Partitioned Fast Convolution Filtering for the Teensy [however I am not sure whether there are still bugs in there, probably this is the case! https://forum.pjrc.com/threads/57267...t=convolution]. The Teensy 3.6 can handle FIR filter lengths as long as 4096 coefficients (but then needs 80% of its memory and 41% processor load at 48ksps sample rate). I am not sure whether such long filter lengths are possible in the time domain. The code for the Teensy 3.6 is on github: https://github.com/DD4WH/Teensy_Convolution For me, it would be very helpful if some of you could have a look at the Partitioned Convolution Code, because it is probable that I am missing something there . . .

    I am looking forward to testing your AudioSDR code with a real SDR hardware frontend. I tested your examples and they run very nicely! I would have tested more, but my T4 plus audio shield combination still does not work properly (drizzling&crackling noise) and I do not know what causes this. I have invested considerable time in that . . .

    All the best,

    Frank DD4WH

  9. #9
    Junior Member
    Join Date
    May 2017
    Location
    Spokane Valley, WA
    Posts
    10
    Hi Frank,

    I have a similar question to your "1.)" above. The additional 90 degree phase shift from the Hilbert shouldn't be required if the input stream is already in quadrature form. ??
    I'm not a fan of using the HT at baseband because of the required roll-off near DC. There are those that insist communications audio only exists from 300-3000 Hz, but I'm not one of them. :-)

    Over the years I've had two projects where it wasn't possible to get the input stream in quadrature form. In one case I used the traditional HT in the Q channel, (and a corresponding delay in the I channel) but in the other case I used "phase added" 45-degree HTs in both channels at an IF frequency, then used a complex mixer to bring the quadrature stream down to baseband for demodulation. I've always disliked the first case, although it met the customer's specification at the time. The second case I can live with, but I would still strongly prefer to have the original stream in quadrature form and skip the second conversion *if I can*. Unfortunately, properly balanced ADCs are more expensive and complicated to implement for hobby designs, so those usually end up being dual conversion.

    That's my story, anyway. I haven't studied Derek's code in detail, but I'm pleased to see others thinking along the same lines.

    Jim N7CXI

  10. #10
    Senior Member DD4WH's Avatar
    Join Date
    Oct 2015
    Location
    Central Europe
    Posts
    445
    Quote Originally Posted by audiomath View Post
    The additional 90 degree phase shift from the Hilbert shouldn't be required if the input stream is already in quadrature form. ??
    As far as I understand, it is indeed necessary in order to cancel out the opposite sideband in SSB. But if I understand it correctly, in the Convolution filter, the complex filtering of both channels already does the job of cancelling the opposite side band.

    Over the years I've had two projects where it wasn't possible to get the input stream in quadrature form. In one case I used the traditional HT in the Q channel, (and a corresponding delay in the I channel) but in the other case I used "phase added" 45-degree HTs in both channels at an IF frequency, then used a complex mixer to bring the quadrature stream down to baseband for demodulation. I've always disliked the first case, although it met the customer's specification at the time. The second case I can live with, but I would still strongly prefer to have the original stream in quadrature form and skip the second conversion *if I can*.
    If you use +45 and -45 degrees Hilbert transforms on the IF band AND your IF is sample rate / 4, you can translate your audio to baseband at nearly no processing cost by using frequency translation without multiplication as described by Rick Lyons:

    https://flylib.com/books/en/2.729.1/...plication.html

    Code:
          /**********************************************************************************
              Frequency translation by Fs/4 without multiplication
              Lyons (2011): chapter 13.1.2 page 646
           **********************************************************************************/
          // this is for +Fs/4 [moves receive frequency to the left in the spectrum display]
          for (unsigned i = 0; i < BUFFER_SIZE * N_BLOCKS; i += 4)
          { // float_buffer_L contains I = real values
            // float_buffer_R contains Q = imaginary values
            // xnew(0) =  xreal(0) + jximag(0)
            // leave as it is!
            // xnew(1) =  - ximag(1) + jxreal(1)
            hh1 = - float_buffer_R[i + 1];
            hh2 =   float_buffer_L[i + 1];
            float_buffer_L[i + 1] = hh1;
            float_buffer_R[i + 1] = hh2;
            // xnew(2) = -xreal(2) - jximag(2)
            hh1 = - float_buffer_L[i + 2];
            hh2 = - float_buffer_R[i + 2];
            float_buffer_L[i + 2] = hh1;
            float_buffer_R[i + 2] = hh2;
            // xnew(3) = + ximag(3) - jxreal(3)
            hh1 =   float_buffer_R[i + 3];
            hh2 = - float_buffer_L[i + 3];
            float_buffer_L[i + 3] = hh1;
            float_buffer_R[i + 3] = hh2;
          }

  11. #11
    Senior Member
    Join Date
    May 2016
    Posts
    218
    Hi guys, You ask some great questions! I'd love to keep discussing these, but I just don't know what the best forum would be. Is the Teensy forum the appropriate place - because these are broader DSP/SDR issues?

    I'll do my best to answer briefly...
    From Jim:
    I'm interested in doing an updated embedded package on an ARM for physical portability and to get the latency down.
    So am I. I was right at the point of purchasing the latest/greatest STM Disco board with the Cortex M7 (I forget the number - it cost about $90), but it was out of stock everywhere. It had the on-board display, and codec - so it seemed to be ideal. And then the T4 came along...

    Jim again:
    The additional 90 degree phase shift from the Hilbert shouldn't be required if the input stream is already in quadrature form
    Here's the deal: Think about where the (non-existent) carrier signal would sit with respect to the audio for a LSB vs a USB signal. Also think about a two-sided complex (quadrature) spectrum with positive and negative frequencies, such as we see on a panadapter display.
    To demodulate SSB, we first frequency-shift the signal down to base-band so that implicit carrier sits at 0 Hz. The LSB signal now sits in the negative frequency region, while the USB will remain in the positive region. (Some sketches here would really help )
    But what does a negative frequency really mean? High school trigonometry tells us that
    cos(2pi x (-f) x t) = cos(2pi x f x t) (no change) but
    sin(2pi x (-f) x t) = -sin(2pi x f x t) ( a change in sign, ie a 180 deg phase change).
    Now we take the Hilbert transform, which adds another 90 deg to one of the channel (say the Q), and voila the I and Q channels are either in phase, or 180 deg out of phase, depending on whether it was a USB or LSB signal.

    The result is that simple addition (or subtraction) of the I and Q signals will result in cancellation of all signals on one side (the out of phase side) and reinforcement of the signal components on the other side (the desired sideband).

    In other words, the Hilbert transform greatly improves the SDR selectivity by eliminating signals on one side of the tuning frequency.

    From Frank:
    Where is the Hilbert transform in Fast convolution filtering in the frequency domain when demodulating SSB?
    I don't know where it is - but I know where it should be! (just kidding) It should be in the same place in the sequence as in the time domain. If it's not there - you may/will get demodulated audio, BUT you will not get the unwanted signal rejection described above.
    Now, there is a very simple frequency domain HT algorithm, that is in fact the way that MATLAB's hilbert() function works. Mathematically the HT generates an "analytical" waveform, which is a complex (quadrature) waveform that has no negative frequency components. So the algorithm simply zonks (that's a technical term) all negative frequencies (with a simple transition at 0 Hz and the Nyquist frequency). Real easy - and it works - except it causes a (very audible) transient at the ends of the data record. I spent many days trying to minimize the transient, without success, so went back to MATLAB and found that it had exactly the same transients. That's when I gave up and went back to pure time domain processing.

    For Frank:
    ... your IF is sample rate / 4, you can translate your audio to baseband at nearly no processing cost by using frequency translation without multiplication as described by Rick Lyons
    Absolutely, that's an old trick, and I thought seriously about using it. The reason I didn't was rather trivial: I thought the high IF frequency would make the panapter display very asymmetrical, with only 5 kHz visible above the tuning region and 28 kHz below it. I could certainly convinced to change it, if it provided a significant processing speed-up.

    This is way too long, more later...

  12. #12
    Junior Member
    Join Date
    May 2017
    Location
    Spokane Valley, WA
    Posts
    10
    Derek,
    Yes, I just stuck my foot in it. It probably doesn't sound like it, but I've done a dozen or so SDR implementations... Only one for amateur radio, though - the Multus Proficio. It uses 96K on receive and 48K on transmit, and the RX has a 12 kHz fixed offset to keep the user from getting close to all the garbage at DC, even with a very wide IF filter. It does make the fish-finder look a bit strange, though...

    Frank, to answer *your* question most FFT convolution schemes use brute force to remove the opposite sideband by zeroing out the correct bins in the freq domain. That "terrorized" data is then complex-multiplied with the impulse response from the IF filter and finally an inverse FFT run to get it back into the time domain. I suspect you already knew most of that, but the relevant fact is that no phase shifting is required when you just "wipe out" the undesired sideband.

    Derek again - the reason I impuned the HT in my previous posting is because it's almost impossible to get good low-frequency audio response unless the demod is done at some higher IF then mixed to baseband.
    Doing so adds complexity and overhead. You mentioned Weaver... I've done two Weavers in the lab, but never could get things adjusted "just so" where the aliasing didn't leak -just a little- into the recovered audio near the top of the output filter. It's possible I didn't use a square enough output filter, of course. (I tried both dual filters after the 1st set of mixers and a single LPF at the output, no audible difference)

    I don't know how the management feels about using this forum like this, but if it helps the common goal here is a full-function SDR implementation that runs on a Teensy - probably v4 from the way the wind is blowing.

    Thanks es 73,
    Jim N7CXI

  13. #13
    Senior Member
    Join Date
    May 2016
    Posts
    218
    Just to keep the thread on topic, here a couple of photos of the construction of the AudioSDR BareBonesWSPR T4 construction:
    Click image for larger version. 

Name:	Top view.jpg 
Views:	30 
Size:	64.8 KB 
ID:	17483
    and bottom view
    Click image for larger version. 

Name:	Bottom view.jpg 
Views:	16 
Size:	64.3 KB 
ID:	17484
    the right hand board holds the QRP Labs receiver module and their 40m band-pass filter. The right hand board holds the T4 and the Teensy Audio Board. Because everything is a prototype, all modules are socketed. The BNC connector on the left goes to the mag-loop antenna, and the audio jack on the left goes to the PC audio line-in, and the WJST-X software.
    Signal wiring is done with 30 AWG wire-wrap wire, power wiring is done with 22 AWG. Bypass caps are installed at each chip. I don't seem to have any crackles or other noises that others have reported.

    The system has been running continuously for five days now and continues to haul in the WSPR stations, the most distant have been a couple of stations in southeast Australia, and a couple in South Africa.
    Here's a screenshot of the spectrum and waterfall display of some incoming WSPR signals.
    Click image for larger version. 

Name:	Waterfall.jpg 
Views:	18 
Size:	249.4 KB 
ID:	17481
    The vertical bands in the top are individual signals in the designated 2 minute lntervals (starting on even minutes UTC), and you can see that we are receiving several different stations at any time. The total width of the WSPR band is only 200 Hz (between 1400 and 1600 Hz in the display above), and each signal occupies only 6 Hz. bandwidth. The transmitters are VERY low power, typically less than 1 watt, and so the received signals are extremely weak.
    Each transmission contains information about the station's location, and the following map shows the locations received by the T4 AudioSDR BareBonesWSPR configuration shown above, on the 40m ham band using an indoor antenna.
    Click image for larger version. 

Name:	Locations.png 
Views:	20 
Size:	257.6 KB 
ID:	17482

  14. #14
    Junior Member
    Join Date
    May 2017
    Location
    Spokane Valley, WA
    Posts
    10
    Very impressive!
    Five days of runtime means a pretty robust system.

    Do you know if the WSPR front-end includes an adaptive resampler? Not that a sample slip here or there will matter much, just curious.
    I haven't used WSPR that much.

    --jim

  15. #15
    Junior Member
    Join Date
    Aug 2019
    Posts
    5
    This is an impressive undertaking and the results are equally impressive.
    The participants in this discussion have knowledge of the inner workings / underlying mathematical concepts etc. at a level I will never fully comprehend, however I find the discussion both interesting and I do learn tidbits of information that are useful - so I hope the discussion continues!
    However, if I may, I'd like to bring the discussion back to a much lower level of broader practical use and application.
    If I understand correctly, in summary, this provides the demodulation aspect of the SDR. In the suggested use case of a SoftRock such as the Ensemble or RxTx, there is (presently) no provision for the frequency control of the SR via its USB port, or a UI to control the SR - i.e. there is no frequency agility without further addition. (Correct?)
    The concept of making a fully functional stand alone and portable SDR based on the SR and a Teensy with a TFT UI is intriguing. The USB dll developed by PE0FKO for the PC platform to configure the Si570 and provide the frequency agility seems like a significant undertaking in itself to port to the Teensy. Any thoughts? Maybe it is easier than I think? The source is in C and is freely available I believe.
    Another and simpler to achieve application also comes to mind. A transceiver like the Elecraft KX3 has an IQ output available. Implementing the Teensy based demodulator with the addition of a display and UI could provide a panadapter and fully independent sub receiver (within the bandwidth of the IQ). Does this sound like a reasonable application / extension of this development? It seems this could be of interest to a sizeable audience.
    Any comments or other suggested uses in response are appreciated.
    Congratulations on your success with this project!
    Tom N0BS

  16. #16
    Senior Member
    Join Date
    May 2016
    Posts
    218
    Thanks for the nice comments, Tom.

    I played with the Soft-Rock kits a few years ago, and as I recall they had a SI570 chip on board, and I'm trying to recall how it was tuned??? Maybe it was through the USB, but it also had an ATtiny that I think just controlled the BPF's. It's all getting to be a bit fuzzy now In those days I was using an Arduino Due as the SDR processor...

    There is a substantial complete Teensy stand-alone SDR package, with all the bells and whistles such as a panadapter, touch-screen menu system, etc, that goes along with AudioSDR. I have not published the whole package because, with the advent of the T4, I will have to build a whole new hardware system, including a new front panel, larger display, etc, and I didn't want to publish that part of it while it's all about to change. Whatever I do, AudioSDR will remain the heart of the signal processing, however.

    You mention the SI570. It has now been superseded by newer chips such as the SI5351 (Adafruit sells a Si5351 module for just a few dollars), and there are several software libraries that do everything for you, writing the code for tuning is trivial just a single line such as:
    Tuner.setFrequency(7150000);
    Pair that with a rotary encoder, and a means of setting the frequency step size, and you're good to go. It's also easy to calibrate: I have mine calibrated to within 1Hz across the whole HF spectrum.
    As far as creating the IQ signal that's easy also, in fact I program the SI5351 as a quadrature IQ generator directly, with no further hardware required.

    So, for the next few months I won't be working on AudioSDR directly, but rather the "wrapper" around the new hardware upgrade.
    I expect to be posting here frequently as the work progresses...
    Regards,
    Derek

  17. #17
    Junior Member
    Join Date
    Aug 2019
    Posts
    5
    Derek, thanks for the reply and all the additional information on where your ongoing development is going. It's exciting to hear of the prospect of the complete wrapper you describe!
    Yes, the SoftRocks use the Si570 to generate the LO signal, and the ATtiny is used to control the BPF as well as to provide an interface from USB to I2C for the Si570 control. Yes, the Si570 is an older generation device at this point, but it was very rugged and tolerant of high temps during soldering, so was good for hobbyist builds :-)
    I have one of the Adafruit Si5351 modules laying around just waiting to be used. Using it to control one of the older generation SoftRocks that only had crystal control might be a good experiment and learning platform. I brought up the SoftRocks mainly because of the rather large "installed base" (reportedly about 10,000) that could represent a target audience for you development. It also represents the possibility of a complete transceiver platform. At this point, there are probably better options however.
    I also have a T4 on the bench just begging for a good dedicated use, and building up an SDR with it is a very good prospect. If / when you are looking for someone to help test, please keep me in mind.
    I'd be glad to help as soon as you have anything ready to share, even early stage stuff.
    Please keep us informed of your progress! Have you selected a display device yet? I'd be interested to hear what direction you are going there.
    Thanks, Tom

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •