Author Topic: ESP32-A1S audio dev board  (Read 6198 times)

dschwartz

Re: ESP32-A1S audio dev board
« Reply #60 on: April 05, 2020, 08:53:47 PM »
Hey Larry
Did you tried this?
https://github.com/hamuro80/blackstomp

This guy has been working a lot with the esp32a1s

thanks for the link. Although it's not solving our Faust issue, it's a good starting point to code some effects in plain C, using the arduino IDE

I will probably use it to recycle some code I built long ago for the dsPIC series...

Mat
Any success?
I loaded the sketch via arduino to my esp32 board and it worked!
I set the MIC1 as the input for left to right and now i have a nice mic-headphone amplifier..but i can't find any useful example dsp code to put in there and learn the mechanic of it.
----------------------------------------------------------
Tubes are overrated!!

http://dsmnoisemaker.blogspot.com

potul

Re: ESP32-A1S audio dev board
« Reply #61 on: April 06, 2020, 02:33:29 AM »
I've been busy with other stuff, so I didn't progress much on this one.
I would start with programming a basic delay line in C (search for C examples, you don't need them to be ESP32 specific). Well you might need to check how to use the memory in the ESP32 board for the delay.

Once you have a working delay line, you can use it for all kind of effects.


Digital Larry

Re: ESP32-A1S audio dev board
« Reply #62 on: April 06, 2020, 10:37:28 AM »
Daniel,

I was thinking of trying the Univibe code here:

https://www.musicdsp.org/en/latest/Effects/277-univox-univibe-emulator.html

I've been plugging away gradually at getting better at Faust.  There is a Faust IDE now which has a code editor, block diagram viewer, oscilloscope and spectrogram viewer and ability to process WAV files, looped or not, or a live microphone.

https://ccrma.stanford.edu/~rmichon/publications/doc/WAC-19-ide.pdf

Yesterday I also discovered that you need to crank the line in gain all the way up to get any level through the board.  I'm settling for a single flanger at the moment.  One challenge for using this board as a pedal is "how do you get pot controls into it"?  I looked at the audio board schematic.  There's a header on the board that brings out a few GPIOs but AFAIK none of these are the ones that go to the ADC.  So you'd probably be left using an external A2D->I2C chip and then bring that in, with accompanying low level programming, which is NOT where my head is at.  One could conceivably use the 6 buttons on the board for up/down on 3 params, or add a display and "whatever" but again that's not something I feel up to tackling.

Nevertheless, so far, I got a really nice single flanger running and a little later today I'll upload the code that did it.

DL
DL

 
Digital Larry
DSP tinkerer and former transistor twister

Digital Larry

Re: ESP32-A1S audio dev board
« Reply #63 on: April 08, 2020, 11:15:19 AM »
Yesterday I tried out the wifi smart_config example included with ESP-IDF.  This uses an Android/IOS app to connect the ESP32 to your WLAN, and it works pretty well.  I also incorporated the code (miraculously) into my Faust based project, but haven't tested it yet.  The only reason this would matter to anyone is that it theoretically allows OSC to be used for parameter adjustment, so no pots or ADC needed.  Not great for a pedal, but for a little board on a piece of plexiglass sitting by my guitar rig it's OK.  Only challenge is that the ESP32 port of Faust doesn't currently include OSC, but a guy is working on it, and I'm ready to assist.

Ultimately, who knows if the external RAM can be made to work easily with Faust, and how much CPU horsepower the thing really has?  My Faust based examples seemed to run out of power (hitting watchdog resets) fairly quickly, while there's that "Blackstomp" pedal that seems to be much more powerful.  My only hesitation with that one rests partially in belief, since whoever published the video didn't include any FX source code.

DL
Digital Larry
DSP tinkerer and former transistor twister

Digital Larry

Re: ESP32-A1S audio dev board
« Reply #64 on: April 12, 2020, 11:44:16 AM »
I remember in the early days of messing with the FV-1 getting mad because I thought the chip was getting the best of me.  Well I showed the FV-1.  A similar thing is happening with the ESP32.  I have no intention of going commercial, and I am not sure I really NEED to develop FX for this little board, but there is a certain advantage compared to writing Jack apps for Linux, because that tends to have unpredictable xruns, aka audio glitches, which I have very low tolerance for.  And the ESP32 was starting to make me MAD!

I was able to integrate some open source button processing code I found on Github, and work through how to get it to work on the AI Thinker board.  I only chose the AI Thinker board because that's what was shown on the Faust page describing ESP32 support.  If you want to get much out of the AI Thinker web site you'd better be able to read Chinese.  Even all of their "app notes" are simply links off to Espressif's app notes for the Lyra line of boards.

So, now I can process 6 buttons on the board.  In the context of a flanger, I'd use these for up/down control of:
a) initial delay (flanger tune)
b) LFO rate
c) LFO width

Yeah, mix and feedback would be nice to have as well (sigh).  Maybe there's a way without having to connect anything else up to the board.  Anyway, stay tuned, not all is lost.

DL
« Last Edit: April 12, 2020, 11:47:42 AM by Digital Larry »
Digital Larry
DSP tinkerer and former transistor twister

dschwartz

Re: ESP32-A1S audio dev board
« Reply #65 on: April 12, 2020, 09:59:09 PM »
If you can't do it, no one can.. you're the best!

We really appreciate your contribution to the community here!!
You could make 1 button to toggle between "increase/ decrease", and the other 5 for each parameter..
----------------------------------------------------------
Tubes are overrated!!

http://dsmnoisemaker.blogspot.com

Digital Larry

Re: ESP32-A1S audio dev board
« Reply #66 on: April 13, 2020, 12:29:31 AM »
Haha... send money!

I'm not all that hot on a push button interface... it's just stopgap until I think of something else.  There's a 12 channel I2C ADC for pretty cheap, I think 6 or 8 pots might be OK.  I know I said I didn't want to build anything, but I do change my mind a lot.  OSC would be the best option but it's not implemented yet in Faust on the ESP32 AFAIK.

DL
Digital Larry
DSP tinkerer and former transistor twister

dschwartz

Re: ESP32-A1S audio dev board
« Reply #67 on: April 13, 2020, 12:53:14 AM »
And a multiplexer for 1 available adc gpio?
----------------------------------------------------------
Tubes are overrated!!

http://dsmnoisemaker.blogspot.com

Re: ESP32-A1S audio dev board
« Reply #68 on: April 13, 2020, 12:44:19 PM »
Not that this is going to be a huge help or possibly already been thought of - It only takes a flip flop chip and 2 spare NAND gates to convert a rotary encoder into an up & down signal. Encoders are used one all the commercial pedals and equipment instead of using pots. The ones like below also have the momentary push button also commonly used for guitar effects.

https://electronics.stackexchange.com/questions/231803/rotary-encoder-to-2-buttons





https://au.element14.com/bourns/pec11r-4115f-s0018/incremental-encoder-2ch-18pulse/dp/2474846?gclid=EAIaIQobChMI9IKwvufl6AIV1XwrCh0dKg4_EAQYAyABEgJNTfD_BwE&mckv=s_dc|pcrid|380892800521|pkw||pmt||slid||product|2474846|pgrid|76493923343|ptaid|aud-451860875263:pla-363010435644|&CMP=KNC-GAU-GEN-SHOPPING-TEST-NEW


Digital Larry

Re: ESP32-A1S audio dev board
« Reply #69 on: April 13, 2020, 01:41:43 PM »
Yeah, thanks for the info.  I get all tweaked out about UI concerns.  For example, the FV-1 having only 3 pots requires a fair number of tradeoffs once you go past trivial FX implementations.  I'm not sure how far I'm going to get with FX on the ESP32, but for example, the envelope controlled flanger already has 7 or 8 controls in the Jack/Qt version I did.  I'm not trying to make a guitar modeler with the ESP32.  At this time I'm hoping it can be convinced to do different types of modulation algos to use in my Eleven rack FX loop.

With pots and buttons and encoders and LEDs there's always the "what am I adjusting" dilemma unless you attach an LCD readout or put labels on the pots, the function of which would always be changing depending on which patch was uploaded.  I'm actually OK with the idea of adding some pots and an I2C LCD of some sort.  Whether or not that is the ultimate solution one could achieve with an ESP32, I don't know.

I really liked the UI on my Line6 M9 as regards using a few pots and switches and an LCD to allow you to tweak a LOT of different things.

I did look at the Blackstomp project that the one guy put up and it's interesting.  It uses the ESP32-A1S module directly so you get access to a bunch of analog inputs that the AI Thinker audio board does not bring out.  That circuit actually does look pretty cool, although I cannot figure out how you upload code to it as there is no USB interface.

DL
Digital Larry
DSP tinkerer and former transistor twister

dschwartz

Re: ESP32-A1S audio dev board
« Reply #70 on: April 13, 2020, 01:54:50 PM »
I have spoken with the blackstomp creator and he tells me that it will be a propietary code, so he will sell pre-programmed esp32s included with the kit, and supply a programming environment for dsp codes ..or something like that.
Anyway, the pcb is pretty straightforward..you could add a serial port and use a rs232 usb/serial programmer ..

----------------------------------------------------------
Tubes are overrated!!

http://dsmnoisemaker.blogspot.com

Digital Larry

Re: ESP32-A1S audio dev board
« Reply #71 on: April 13, 2020, 02:19:19 PM »
I have spoken with the blackstomp creator and he tells me that it will be a propietary code, so he will sell pre-programmed esp32s included with the kit, and supply a programming environment for dsp codes ..or something like that.
Anyway, the pcb is pretty straightforward..you could add a serial port and use a rs232 usb/serial programmer ..
Interesting.  Yeah a preprogrammed ESP32 is not so exciting for me, or even some other programming environment, but you're right, I could get some USB to UART cable.  For the time being I am going to keep working with the audio dev board I have and see how far I can get with Faust. 

DL
Digital Larry
DSP tinkerer and former transistor twister

Digital Larry

Re: ESP32-A1S audio dev board
« Reply #72 on: April 13, 2020, 04:40:39 PM »
All right, here's my code for a basic mono flanger which uses the 6 pushbuttons of the AI-Thinker ESP32-A1s audio/wifi/bluetooth dev board to adjust LFO width, speed, and flanger center delay.

The code layout is structured as a typical esp-idf project, with all the source in a "main" folder below the project folder.

Run this command to create basicFlanger.cpp and AC101.cpp:

Code: [Select]
faust2esp32 -lib -ac101 basicFlanger.dsp

This will create a ZIP file - you have to get the files out of it.

Here are the other important contents of the "main" folder.

CMakeLists.txt

Code: [Select]
idf_component_register(SRCS "main.cpp" "AC101.cpp" "basicFlanger.cpp" "button.c" INCLUDE_DIRS "")
main.cpp

Code: [Select]
/* stereoFlanger main

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_spi_flash.h"
#include "driver/gpio.h"
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"

#define DELAYMIN 64
#define DELAYMAX 864
#define LFORATEMIN 0.02
#define LFORATEFACTOR 1.2
#define LFORATEMAX 8.0
#define LFOWIDTHMIN 0.02
#define LFOWIDTHFACTOR 1.2
#define LFOWIDTHMAX 1.0

extern "C" {
#include "button.h"
}

#include "AC101.h"
#include "basicFlanger.h"

extern "C" {
    void app_main(void);
    void initialise_wifi(void);
    QueueHandle_t * button_init(unsigned long long );
}

void app_main(void)
{
    int SR = 32000;
//  BS = 256 seems to be about the maximum you can set it.
//  maybe 384.  I'm setting it higher to try to avoid WDT CPU0 Idle errors
//  when I add more blocks to the DSP file
//  BS = 512 causes stack overflow and reboot
    int BS = 128;
 
int delay = DELAYMIN;
float rate = 1.0;
float width = 0.25;

    AC101 AC101;
//    initialise_wifi();
    AC101.begin();
    AC101.SetVolumeHeadphone(63);
button_event_t ev;
QueueHandle_t button_events = button_init(PIN_BIT(5) | PIN_BIT(13) | PIN_BIT(18)  | PIN_BIT(19) | PIN_BIT(23) | PIN_BIT(36));

    basicFlanger basicFlanger(SR,BS); 
    basicFlanger.start();
       
    while (true) {
if (xQueueReceive(button_events, &ev, 1000/portTICK_PERIOD_MS)) {
// ESP_LOGI("Queue rx", "pin %d", ev.pin);
if ((ev.pin == 36) && (ev.event == BUTTON_DOWN)) {
if (delay > DELAYMIN)
{
delay--;
}
basicFlanger.setParamValue("Delay", delay);
ESP_LOGI("Flanger delay", "Down-> %d", delay);
}
if ((ev.pin == 13) && (ev.event == BUTTON_DOWN)) {
if (delay < DELAYMAX)
{
delay++;
}
basicFlanger.setParamValue("Delay", delay);
ESP_LOGI("Flanger delay", "Up-> %d", delay);
}
if ((ev.pin == 5) && (ev.event == BUTTON_DOWN)) {
if (width < LFOWIDTHMAX)
{
width = width * LFOWIDTHFACTOR;
}
else
{
width = LFOWIDTHMAX;
}
if (width < LFOWIDTHMIN)
{
width = LFOWIDTHMIN;
}
basicFlanger.setParamValue("Width", width);
ESP_LOGI("LFO Width", "Up=>%f", width);
}
if ((ev.pin == 18) && (ev.event == BUTTON_DOWN)) {
if (width > LFOWIDTHMIN)
{
width = width/LFOWIDTHFACTOR;
}
else
{
width = 0.0;
}
basicFlanger.setParamValue("Width", width);
ESP_LOGI("LFO Width", "Down=>%f", width);
}
if ((ev.pin == 19) && (ev.event == BUTTON_DOWN)) {
if (rate > LFORATEMIN)
{
rate = rate/LFORATEFACTOR;
}
else
{
rate = 0.0;
}
basicFlanger.setParamValue("Rate", rate);
ESP_LOGI("LFO Rate", "Down->%f", rate);
}
if ((ev.pin == 23) && (ev.event == BUTTON_DOWN)) {
if (rate <  LFORATEMIN)
{
rate = LFORATEMIN;
}
if (rate <  LFORATEMAX)
{
rate = rate * LFORATEFACTOR;
}
basicFlanger.setParamValue("Rate", rate);
ESP_LOGI("LFO Rate", "Up->%f", rate);
}
}
    }
}

button.c
Code: [Select]
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "esp_log.h"

#include "button.h"

#define TAG "BUTTON"

typedef struct {
uint8_t pin;
    bool inverted;
uint16_t history;
    uint64_t down_time;
} debounce_t;

int pin_count = -1;
debounce_t * debounce;
QueueHandle_t * queue;

static void update_button(debounce_t *d) {
    d->history = (d->history << 1) | gpio_get_level(d->pin);
}

#define MASK   0b1111000000111111
static bool button_rose(debounce_t *d) {
    if ((d->history & MASK) == 0b0000000000111111) {
        d->history = 0xffff;
        return 1;
    }
    return 0;
}
static bool button_fell(debounce_t *d) {
    if ((d->history & MASK) == 0b1111000000000000) {
        d->history = 0x0000;
        return 1;
    }
    return 0;
}
static bool button_down(debounce_t *d) {
    if (d->inverted) return button_fell(d);
    return button_rose(d);
}
static bool button_up(debounce_t *d) {
    if (d->inverted) return button_rose(d);
    return button_fell(d);
}

#define LONG_PRESS_DURATION (1000)

static uint32_t millis() {
    return esp_timer_get_time() / 1000;
}

static void send_event(debounce_t db, int ev) {
    button_event_t event = {
        .pin = db.pin,
        .event = ev,
    };
    xQueueSend(queue, &event, portMAX_DELAY);
}

static void button_task(void *pvParameter)
{
    while (1) {
        for (int idx=0; idx<pin_count; idx++) {
            update_button(&debounce[idx]);
            if (debounce[idx].down_time && (millis() - debounce[idx].down_time > LONG_PRESS_DURATION)) {
                debounce[idx].down_time = 0;
//               ESP_LOGI(TAG, "%d LONG", debounce[idx].pin);
                int i=0;
                while (!button_up(&debounce[idx])) {
//                    ESP_LOGI(TAG, "debounce %d i: %d", debounce[idx].pin, i);
    if (!i) send_event(debounce[idx], BUTTON_DOWN);
                    i++;
                    if (i>=5) i=0;
                    vTaskDelay(20/portTICK_PERIOD_MS);   // debug, originally 10
                    update_button(&debounce[idx]);
                }
//                ESP_LOGI(TAG, "%d UP", debounce[idx].pin);
                send_event(debounce[idx], BUTTON_UP);
            } else if (button_down(&debounce[idx])) {
                debounce[idx].down_time = millis();
//                ESP_LOGI(TAG, "%d DOWN", debounce[idx].pin);
                send_event(debounce[idx], BUTTON_DOWN);
            } else if (button_up(&debounce[idx])) {
                debounce[idx].down_time = 0;
//                ESP_LOGI(TAG, "%d UP", debounce[idx].pin);
                send_event(debounce[idx], BUTTON_UP);
            }
        }
        vTaskDelay(10/portTICK_PERIOD_MS);
    }
}

QueueHandle_t * button_init(unsigned long long pin_select) {
    if (pin_count != -1) {
        ESP_LOGI(TAG, "Already initialized");
        return NULL;
    }

    ESP_LOGI(TAG, "configuring GPIOs");
    // Configure the pins
    gpio_config_t io_conf;
    io_conf.mode = GPIO_MODE_INPUT;   
io_conf.pull_up_en = 1;
io_conf.pull_down_en = 0;
    io_conf.pin_bit_mask = pin_select;
    gpio_config(&io_conf);

    // Scan the pin map to determine number of pins
    pin_count = 0;
    for (int pin=0; pin<=39; pin++) {
        if ((1ULL<<pin) & pin_select) {
            pin_count++;
        }
    }
ESP_LOGI(TAG, "Pin count: %d", pin_count);

    // Initialize global state and queue
    debounce = calloc(pin_count, sizeof(debounce_t));
    queue = xQueueCreate(4, sizeof(button_event_t));

    // Scan the pin map to determine each pin number, populate the state
    uint32_t idx = 0;
    for (int pin=0; pin<=39; pin++) {
        if ((1ULL<<pin) & pin_select) {
            ESP_LOGI(TAG, "Registering button input: %d", pin);
            debounce[idx].pin = pin;
            debounce[idx].down_time = 0;
            debounce[idx].inverted = true;
            if (debounce[idx].inverted) debounce[idx].history = 0xffff;
            idx++;
        }
    }

    // Spawn a task to monitor the pins
    xTaskCreate(&button_task, "button_task", 4096, NULL, 10, NULL);

    return queue;
}

button.h
Code: [Select]
#define PIN_BIT(x) (1ULL<<x)

#define BUTTON_DOWN (1)
#define BUTTON_UP (2)

typedef struct {
uint8_t pin;
    uint8_t event;
} button_event_t;

QueueHandle_t * button_init(unsigned long long );

basicFlanger.dsp
Code: [Select]
import("stdfaust.lib");
flaDelay = hslider("[3]Delay", 156, 5, 1000, 1) : si.smoo;
flaFeedback = hslider("[4]Flange Fb", 0.8, 0, 0.97, 0.01) : si.smoo;
flaDepth = hslider("[5]Flange Dep", 0.95, 0, 1.0, 0.01) : si.smoo;
flaLFORate = hslider("[6]Rate", 0.25, 0, 3, 0.01) : si.smoo;
flaLFOWidth = hslider("[7]Width", 0.5, 0, 1.0, 0.01) : si.smoo;
flaLFO = os.lf_triangle(flaLFORate);
flaMod = flaLFOWidth * (flaLFO/2) ;
flanger = pf.flanger_mono(512, flaDelay * (1 + flaMod), flaDepth, flaFeedback, 1);
//=============================================
process =  _,_: + :> flanger <: _,_;
« Last Edit: April 13, 2020, 04:43:14 PM by Digital Larry »
Digital Larry
DSP tinkerer and former transistor twister

dschwartz

Re: ESP32-A1S audio dev board
« Reply #73 on: April 13, 2020, 04:47:05 PM »
Wowww
Amazing!!!
I'm trying to wrap my head around the button.c code..

It is just for debounce ?
----------------------------------------------------------
Tubes are overrated!!

http://dsmnoisemaker.blogspot.com

Digital Larry

Re: ESP32-A1S audio dev board
« Reply #74 on: April 13, 2020, 05:18:00 PM »
Here's the source of the button code:

https://github.com/craftmetrics/esp32-button

Digital Larry
DSP tinkerer and former transistor twister

Digital Larry

Re: ESP32-A1S audio dev board
« Reply #75 on: April 13, 2020, 10:43:05 PM »
Here's two flangers in series, identical except the sweep is in the opposite direction from each other.

Code: [Select]
import("stdfaust.lib");
flaDelay = hslider("[3]Delay", 156, 5, 1000, 1) : si.smoo;
flaFeedback = hslider("[4]Flange Fb", 0.8, 0, 0.97, 0.01) : si.smoo;
flaDepth = hslider("[5]Flange Dep", 0.95, 0, 1.0, 0.01) : si.smoo;
flaLFORate = hslider("[6]Rate", 0.25, 0, 3, 0.01) : si.smoo;
flaLFOWidth = hslider("[7]Width", 0.5, 0, 1.0, 0.01) : si.smoo;
flaLFO = os.lf_triangle(flaLFORate);
flaMod = flaLFOWidth * (flaLFO/2) ;
flanger(x) = pf.flanger_mono(512, flaDelay * (1 + (x * flaMod)), flaDepth, flaFeedback, 1);
flange = hgroup("Flange", flanger(1) : flanger(-1));
//=============================================
process =  _,_: + :> flange <: _,_;
Digital Larry
DSP tinkerer and former transistor twister

Digital Larry

Re: ESP32-A1S audio dev board
« Reply #76 on: April 14, 2020, 10:25:38 AM »
I just found a configuration option (under "make menuconfig") that allows you to set the external SPI RAM to be usable by malloc().  At first glance this allows configuration of delays up to about 2.5 seconds using ef.echo(), whereas previously I could only get about 150 msec.  However, 3.5 seconds is too much.  This is OK.  The FV-1 only has 1 second in most cases unless you undersample.

Under:
Code: [Select]
Component config → ESP32-specific → SPI RAM config

Select:
Code: [Select]
(X) Make RAM allocatable using malloc() as well

One thing I like to do with delays is hang a different modulation or filter off each of several taps.  I was about to hang it up on this thing!

I was able to get a jcrev simple reverb block working.  Next I tried a mono_freeverb and wasn't quite as lucky.  Sound is distorted and I started getting watchdog timer errors, indicating most likely that there's too much processing being attempted.
« Last Edit: April 14, 2020, 11:27:36 AM by Digital Larry »
Digital Larry
DSP tinkerer and former transistor twister

audioartillery

Re: ESP32-A1S audio dev board
« Reply #77 on: April 14, 2020, 04:38:26 PM »
There's a header on the board that brings out a few GPIOs but AFAIK none of these are the ones that go to the ADC.  So you'd probably be left using an external A2D->I2C chip and then bring that in, with accompanying low level programming, which is NOT where my head is at.

I had a similar problem on my Blackaddr-based build.  It had some ADC lines exposed but not enough, so I put down an i2c controlled mux.  Not really a big deal to sample all the pots and switches.

Might be just as easy to put down a small Arduino and rig up some i2c or SPI communication with that, then let it manage the UI.

Digital Larry

Re: ESP32-A1S audio dev board
« Reply #78 on: April 14, 2020, 06:59:52 PM »
Rumor has it that this board is susceptible to audio crosstalk from both wifi and I2C, which would be too bad.  Not sure why I'd want to add an Arduino, things are tough enough learning freeRTOS.  :icon_cool:  In any case, there are 2 cores and DSP and UI code can be isolated to one or the other.  I'm still having a good time messing around with it just the way it is.
Digital Larry
DSP tinkerer and former transistor twister

dschwartz

Re: ESP32-A1S audio dev board
« Reply #79 on: April 15, 2020, 12:46:09 PM »
i think using the LPU processor is a thing reserved for geniuses only.. but i know you are one :P
----------------------------------------------------------
Tubes are overrated!!

http://dsmnoisemaker.blogspot.com