Guitar amplifier controls project

Hello everyone, this is a first post for me. I’ll start with a little background. I’m new to Arduino but not to programming, and I have experience with vacuum tube based electronics but not with transistors. My planned project incorporates both an Arduino and transistors, so I’m venturing into new ground.

The Arduino is going to serve three functions:

  • delay the application of the high voltage power supply via a timer and a relay
  • handle channel switching functions by turning on/off banks of vactrols
  • output an lfo for a tremolo that is switchable between sine, sawtooth and square waveforms using filtered pwm

As of now, the sketch is in good shape I think. I have it breadboarded up with just leds to check that things are turning on/off appropriately. I have some questions though, about integrating into the circuit via transistors and applying the lfo.

The amplifier doesn’t use conventional discreet channels, instead I am switching out gain pots, the complete tone contraol stack, and master volume so that in effect there will be two complete sets of controls for one channel, saving on tubes and associated power demand. The catch is that I’m going to be driving a lot of vactrols, ten per channel, and want to do it with a single pin to preserve as much i/o as possible. I’m thinking driving the vactrols from a seperate power supply via npn transistors. I have enough voltage to do two strings of leds per channel. Can I just connect the transistors in parallel, ie the base to the same output pin on the Arduino?

The next question is how to apply the tremolo lfo to the output. I know I can modulate the volume optically, again using vactrols. However, I’d prefer to instead modulate the bias of the output stage as I’ve done with previous amps. The problem is that the grid bias for the output stage has to be kept negative. So I was thinking something very unconventional here. Could I “float” the power for the Arduino on the negative power supply, ie the ground for the Arduino would be referenced to around -12v and the positive rail +5v above that? My idea is Arduino lfo pin → class A transistor voltage amplifier (to boost the 5v lfo up to something more appropriate for vacuum tubes) → grid leak resistors for the output stage.

The Arduino would still be seeing +5v between it’s positive supply and “ground”. Or is this just a bad idea?

You will have no end of trouble with #3 because the PWM frequency is in the audio range. With the Vactrols, how are you going to achieve isolation for the channels that you're not using? A Vactrol cannot achieve infinite resistance. Or is it sufficient to just use them to reduce the gain of any unused channels significantly?

Considering the depth of your inquiry, would it be too much to ask you for a schematic?

Concerning the grid bias, why is the Arduino supply voltage even an issue, if all the amp circuitry is on the photoresistor side of the Vactrol?

Regarding the PWM frequency, it’s a push pull output stage, so I’m relying on a combination of properly filtering the PWM and common mode noise rejection to clean up the artifacts in question.

As for channel switching, it’s actually a tried and true method. Consider two channels feeding the following stage via the resistor portion of a vactrol, one goes low resistance while the other goes high resistance. Obviously there is some leakage, however as long as there is a high amount of change from the low to high states then the voltage divider formed at their junction makes it seem for all intents and purposes that one channel is off. I’m using cds cells and an led and rolling my own, my measurements go from a resistance of 2meg when dark to just a few tens of ohms while lit. Quite effective.

As for your question regarding grid bias, there are two common ways to modulate the output of a tube amp. One is by vactrol and a resistor forming a voltage divider, which I gather is what you have in mind. The other is by directly varying the bias voltage of the output tubes, which is what I would rather do. It has a distinct sound, as when the signal does more positive to tubes are pushed into distortion to a varying amount depending on the strength of the lfo and the strength of the audio signal.

My schematic is a WIP right now, with no power supply drawn up and without even adding the Adruino and it’s power supply.

Can't you vary the grid bias voltage using a voltage divider? Converting resistance to voltage is something a variable resistor voltage divider is capable of.

Also can you give details of your low pass filter? I am still skeptical. What configuration and cutoff frequency do you intend to use?

Everything else looks pretty easy.

Ignoring, for the moment, your concerns about the PWM, your answer did give me the spark of an idea, because you're right, converting resistance to voltage can be done.

  • create two levels of bias voltage
  • use the PWM signal to drive a differential amplifier, driving two vactrols with the actual bias voltage taken at their junction
  • the depth of the effect can be controlled by changing the distance between the two voltages, using a ganged pot.
  • effect off turns both vactrols on, making the junction right around the midpoint of the two bias voltages

This should work I think. It has the benefit of optically isolating the bias voltage from the PWM signal, while still obtaining the volume modulation via bias of the power stage.

Thanks for the help. I'll continue working the schematic and hopefully get this build started before long.

I don't think you need something as fancy as two Vactrols. If an LDR is one leg of a resistive divider you should be able, with some biasing and calibration, to create a linear variable voltage on the amp side. But it is a bit of hand waving without at least a basic output stage schematic.

Couldn't the depth of the effect be controlled in software? Isn't the point of using an Arduino that you could do things like that without additional hardware?

It is not a suitable idea to operate the Arduino with "GND" referenced to anything other than the common ground.

Just use an op-amp to both filter the PWM heavily and invert the voltage using a negative supply (which can be generated by a charge pump - a MAX232 will do that for you. :grinning: )

I actually have a proof of concept circuit working. As I said earlier, I scrapped entirely the idea of floating the ground reference negative.

I also realized that I don’t need anything external to create two anti-phase LFO signals, the Arduino is capable of that on it’s own. I still want to go with the dual LFO setup for a reason. I want the bias voltage that I feed the output tubes to swing both up and down from it’s “off” position, not swing just up or down from where it is at rest. That way when the tremolo is switched on or off there is no perceived increase or decrease in overall volume.

Couldn’t the depth of the effect be controlled in software? Isn’t the point of using an Arduino that you could do things like that without additional hardware?

That’s a good point, but I’m not sure exactly how I would achieve it right now. Square wave would be no problem, and sawtooth I think I could figure out. But the sine wave is being generated via a lookup table. I’m not that great with C++. I’ll go ahead and attach the code I’ve got.

/* 
  * Controlled startup eliminates 'Standby' switch
  * Control two banks of vactrols for channel switching
  * Initialize lfo for tremolo and select waveform
  * 
  * Written 03/2020 by Nathan Fisher <nfisher.sr@gmail.com>
  * 
  * mains relay circuit on pin 12
  * channel switches pin 2 between 5v and ground
  * channel 1 output (relay or vactrol) pin 3
  * channel 2 output (relay or vactrol) pin 4
  * LFO A on pin 5
  * LFO B on pin 6
  * trem on/off switches pin 7 between 5v and ground
  * tremolo indicator led on pin 9
 */

// setup the pins for various purposes
// connect our mains relay circuit to pin 2
const int mainsRelayPin = 12;

// channel switching
const int chswitchPin = 2; // channel switch spst switch on/off
const int ch1Pin = 3; // first bank of vactrols
const int ch2Pin = 4; // second bank of vactrols
int chswitchState = 0; // variable reading channel switch state

// tremolo section
int trswitchState = 0;
int trmode = 0;
const int traPin = 5; // pwm wave here
const int trbPin = 6;
const int trswitchPin = 7; // tremolo on/off switch
const int trindPin = 9;

// sine wave positions
byte sina = 0;
byte sinb = 0;
// the value for each position in the sine and sine reverse tables
byte s = 0;
byte r = 127;
// the tremolo rate
int interval = 10;
// counts the intervals that have passed, increments each time
int loopCount = 0;
// decrements each interval
int looprevCount = 255;

// store last update time
unsigned long previousMicros = 0;

// Our sine wave table for trem
const byte sineTable[] = { 
128, 131, 134, 137, 140, 143, 146, 149, 152, 155, 158, 162, 165, 167,
170, 173, 176, 179, 182, 185, 188, 190, 193, 196, 198, 201, 203, 206,
208, 211, 213, 215, 218, 220, 222, 224, 226, 228, 230, 232, 234, 235,
237, 238, 240, 241, 243, 244, 245, 246, 248, 249, 250, 250, 251, 252,
253, 253, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 254, 254,
254, 253, 253, 252, 251, 250, 250, 249, 248, 246, 245, 244, 243, 241,
240, 238, 237, 235, 234, 232, 230, 228, 226, 224, 222, 220, 218, 215,
213, 211, 208, 206, 203, 201, 198, 196, 193, 190, 188, 185, 182, 179,
176, 173, 170, 167, 165, 162, 158, 155, 152, 149, 146, 143, 140, 137,
134, 131, 128, 124, 121, 118, 115, 112, 109, 106, 103, 100, 97, 93, 90,
88, 85, 82, 79, 76, 73, 70, 67, 65, 62, 59, 57, 54, 52, 49, 47, 44, 42,
40, 37, 35, 33, 31, 29, 27, 25, 23, 21, 20, 18, 17, 15, 14, 12, 11, 10,
9, 7, 6, 5, 5, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2,
3, 4, 5, 5, 6, 7, 9, 10, 11, 12, 14, 15, 17, 18, 20, 21, 23, 25, 27, 29,
31, 33, 35, 37, 40, 42, 44, 47, 49, 52, 54, 57, 59, 62, 65, 67, 70, 73,
76, 79, 82, 85, 88, 90, 93, 97, 100, 103, 106, 109, 112, 115, 118, 121,
124
};

// the setup function runs once when you press reset or power the board
void setup() {
 // initialize digital pin mainsRelayPin as an output.
 pinMode(mainsRelayPin, OUTPUT);
 // wait for the valves to warm up
 delay(15000);
 // switch the relay on
 digitalWrite(mainsRelayPin, HIGH);
 // initialize the vactrol pins as outputs
 pinMode(ch1Pin, OUTPUT);
 pinMode(ch2Pin, OUTPUT);
 pinMode(trindPin, OUTPUT);
 // initialize tremolo pins as output for square wave use
 pinMode(traPin, OUTPUT);
 pinMode(trbPin, OUTPUT);
}


// the program
void loop() {
 // get the channel switch state
 chswitchState = digitalRead(chswitchPin);
 
 // switch banks of vactrols on/off for channel switching
 if (chswitchState == HIGH) {
 digitalWrite(ch1Pin, HIGH);
 digitalWrite(ch2Pin, LOW);
 } else {
 digitalWrite(ch1Pin, LOW);
 digitalWrite(ch2Pin, HIGH);
 }

    unsigned long currentMicros = micros();
 // switch the tremolo on/off
 trswitchState = digitalRead(trswitchPin);
 if (trswitchState == HIGH) {
 interval = (analogRead(1) * 20 + 1);
 if (currentMicros - previousMicros >= interval) {
 // save the last pwm state change time
 previousMicros = currentMicros;
 // reset loopCount and looprevCount at the end of cycle
 if (loopCount >= 255) {
 loopCount = 0;
 }
 if (looprevCount <= 0) {
 looprevCount = 255;
 }
 // increment through the sine wave tables
 s++;
 r++;
 // increment the loop count, for square and saw waves
 loopCount++;
 // decrement the looprev count for sawrev wave
 looprevCount--;
 }
 trmode = (analogRead(2)); // read our tremolo mode switch
 if (trmode < 300) {
 sina = sineTable[s];
 sinb = sineTable[r];
 analogWrite(traPin, sina);
 analogWrite(trbPin, sinb);
 analogWrite(trindPin, sinb);
 } else if (trmode < 600) {
 analogWrite(traPin, loopCount);
 analogWrite(trbPin, looprevCount);
 analogWrite(trindPin, looprevCount);
 } else if (trmode < 900) {
 analogWrite(traPin, looprevCount);
 analogWrite(trbPin, loopCount);
 analogWrite(trindPin, loopCount);
 } else {
 if (loopCount <= 127) {
 digitalWrite(traPin, HIGH);
 digitalWrite(trbPin, LOW);
 digitalWrite(trindPin, LOW);
 } else {
 digitalWrite(traPin, LOW);
 digitalWrite(trbPin, HIGH);
 digitalWrite(trindPin, HIGH);
 }
 }
 } else {
 digitalWrite(trindPin, LOW);
 // set the LFO pins to mid level
 analogWrite(traPin, 127);
 analogWrite(trbPin, 127);
 }
}

I’m sure there are more elegant ways to achieve what I’m doing, but for the moment I’m pretty happy that it’s doing what I want it to.