Phonograph (Stereo)
Using the vibrato object [link], a couple of types of noise generation and some extreme high pass filtering to hopefully get somewhere close to a convincing simulation of an old (and I mean old, like 1920s style) record player.
note: "chorus1" in the GUI diagram was a placeholder for the vibrato object
The noise generation is made up of band-passed pink noise and pseudo-random clicking. The clicking comes from three square wave LFOs (made with the waveform object) fed into a couple of XOR gates using the combine object to create odd patterns. It's then high-pass filtered significantly, with the HPF cutoff frequency being randomised within a set range. The volume of the clicking is also randomised and I've even added a timer to change both of these parameters at random intervals.
The toggle switch in this sketch selects between 3 different highpass & lowpass settings on the guitar signal, kind of lowpass/bandpass/highpass. These options generally skew towards high-pass filters which (to my ears) are more reflective of a grammaphone.
I've attempted to add a needle skip effect with footswitch, so pressing it pulls the needle off the record by maxing out the vibrato depth before the effect goes silent. Releasing the footswitch then does this in reverse. It's not quite there yet. If it's going to sound convincing I really need the ability to reset the vibrato LFO phase to 0 so I can time the pitch bends. If I can figure out how to do this I could also modulate the pink noise volume in time with the vibrato LFO, adding to the illusion of a spinning record.
The needle skip was inspired (shamelessly stolen) from the amazing 'Melusine' pedal by Hexe.
There are many improvements to be made, and I've included plenty of comments in the sketch so it can easily be tweaked to your liking.
#define LED 3
#include <Bounce.h>
#include <vibrato.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
// GUItool: begin automatically generated code
AudioInputI2S i2s1; //xy=253.08334350585938,376.0833435058594
AudioSynthWaveform waveform6; //xy=498.41668701171875,544.0833740234375
AudioSynthWaveform waveform5; //xy=499.41668701171875,496.0833740234375
AudioSynthWaveform waveform4; //xy=501.75,446.0833435058594
AudioSynthWaveform waveform3; //xy=509.75,328.0833435058594
AudioSynthWaveform waveform2; //xy=510.75,280.0833435058594
AudioSynthWaveform waveform1; //xy=513.0833129882812,230.08331298828125
AudioEffectHighQualityVibrato vibrato1;
AudioEffectHighQualityVibrato vibrato2;
AudioEffectDigitalCombine combine4; //xy=674.75,525.0833129882812
AudioEffectDigitalCombine combine3; //xy=680.75,469.0833435058594
AudioSynthNoisePink pink2; //xy=683.75,605.0833129882812
AudioEffectDigitalCombine combine2; //xy=689.75,307.0833435058594
AudioEffectDigitalCombine combine1; //xy=691.0833129882812,248.08334350585938
AudioSynthNoisePink pink1; //xy=702.0833282470703,380.0833282470703
AudioFilterBiquad biquad6; //xy=746.75,694.0833129882812
AudioFilterBiquad biquad3; //xy=750.75,107.08333587646484
AudioFilterBiquad biquad5; //xy=838.75,596.0833129882812
AudioFilterBiquad biquad1; //xy=846.0833129882812,275.0833435058594
AudioFilterBiquad biquad2; //xy=846.75,370.0833435058594
AudioFilterBiquad biquad4; //xy=846.75,502.0833435058594
AudioMixer4 mixer2; //xy=1091.75,537.0833129882812
AudioMixer4 mixer1; //xy=1111.083251953125,294.0833435058594
AudioOutputI2S i2s2; //xy=1282.083251953125,413.0833435058594
AudioConnection patchCord1(i2s1, 0, vibrato1, 0);
AudioConnection patchCord2(i2s1, 1, vibrato2, 0);
AudioConnection patchCord3(waveform6, 0, combine4, 1);
AudioConnection patchCord4(waveform5, 0, combine3, 1);
AudioConnection patchCord5(waveform4, 0, combine3, 0);
AudioConnection patchCord6(waveform3, 0, combine2, 1);
AudioConnection patchCord7(waveform2, 0, combine1, 1);
AudioConnection patchCord8(waveform1, 0, combine1, 0);
AudioConnection patchCord9(vibrato2, biquad6);
AudioConnection patchCord10(vibrato1, biquad3);
AudioConnection patchCord11(combine4, biquad4);
AudioConnection patchCord12(combine3, 0, combine4, 0);
AudioConnection patchCord13(pink2, biquad5);
AudioConnection patchCord14(combine2, biquad1);
AudioConnection patchCord15(combine1, 0, combine2, 0);
AudioConnection patchCord16(pink1, biquad2);
AudioConnection patchCord17(biquad6, 0, mixer2, 0);
AudioConnection patchCord18(biquad3, 0, mixer1, 0);
AudioConnection patchCord19(biquad5, 0, mixer2, 2);
AudioConnection patchCord20(biquad1, 0, mixer1, 1);
AudioConnection patchCord21(biquad2, 0, mixer1, 2);
AudioConnection patchCord22(biquad4, 0, mixer2, 1);
AudioConnection patchCord23(mixer2, 0, i2s2, 1);
AudioConnection patchCord24(mixer1, 0, i2s2, 0);
AudioControlSGTL5000 sgtl5000_1; //xy=1276.083251953125,667.0833129882812
// GUItool: end automatically generated code
Bounce footswitch = Bounce(0, 50); // debounce the footswitch
Bounce D1 = Bounce(1, 50); // debounce the toggle switch
Bounce D2 = Bounce(2, 50); // " " " " " " " " "
// this section includes the function to check the toggle position
bool right;
bool middle;
bool left;
void checkToggle () { // this is our function to check toggle position...
D1.update(); D2.update(); // check digital inputs connected to toggle (can delete I think)
if(digitalRead(1) && !digitalRead(2)) {right = 1; middle = 0; left = 0;} // toggle is right
if(digitalRead(1) && digitalRead(2)) {right = 0; middle = 1; left = 0;} // toggle is in the middle
if(!digitalRead(1) && digitalRead(2)) {right = 0; middle = 0; left = 1;} // toggle is left
}
// noise volumes
float noisevol; // variable to control pink noise volume
float clickvol; // variable to control click volume
// click volume / hpf randomisation
int clickhpf = 8000; // variable for storing randomised hpf cutoff for the clicks
unsigned int randomtime = 0; // this will dictate the number of milliseconds until the next click volume change
unsigned long currentMillis = millis(); // this acts as a millisecond timer
// vibrato
float rate = 1;
float depth = 5;
//needle skip
byte skipping = 0; // will be 1 when in process of skipping pre-silence, 2 post-silence, 0 in normal non-skipping process
void setup() {
AudioMemory(40); // the "40" represents how much internal memory (in the Teensy, not the external RAM chip) is allotted for audio recording. It is measured in sample blocks, each providing 2.9ms of audio.
sgtl5000_1.enable(); // this turns on the SGTL5000, which is the audio codec on the audio board
sgtl5000_1.volume(1); // this sets the output volume (it can be between 0 and 1)
sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN); // selects the audio input, we always use Line In
analogReadResolution(12); // configure the pots to give 12 bit readings
pinMode(0, INPUT_PULLUP); // internal pull-up resistor for footswitch
pinMode(1, INPUT_PULLUP); // internal pull-up resistor for toggle
pinMode(2, INPUT_PULLUP); // internal pull-up resistor for toggle
pinMode(3, OUTPUT); // pin 3 (the LED) is an output;
Serial.begin(9600); // initiate the serial monitor. USB is always 12 Mbit/sec
// click generator
waveform1.begin(1, 0.82, WAVEFORM_SQUARE); // increasing "0.xx" in each of these three will increase the regularity of the clicks...
waveform2.begin(1, 0.42, WAVEFORM_SQUARE); // ... these are prime numbers, intended to sound more random, for what that's worth
waveform3.begin(1, 1.29, WAVEFORM_SQUARE);
combine1.setCombineMode(AudioEffectDigitalCombine::XOR);
combine2.setCombineMode(AudioEffectDigitalCombine::XOR);
biquad1.setLowpass(0, 9800, 0.3); // 9800 is the lpf cutoff for the clicks
biquad1.setHighpass(0, 9000, 0.3); // 9000 is the hpf cutoff for the clicks - GETS RANDOMISED LATER!
mixer1.gain(1,0.5);
// stereo
waveform4.begin(1, 1.87, WAVEFORM_SQUARE); // increasing "0.xx" in each of these three will increase the regularity of the clicks...
waveform5.begin(1, 0.43, WAVEFORM_SQUARE); // ... these are prime numbers, intended to sound more random, for what that's worth
waveform6.begin(1, 0.24, WAVEFORM_SQUARE);
combine1.setCombineMode(AudioEffectDigitalCombine::XOR);
combine2.setCombineMode(AudioEffectDigitalCombine::XOR);
biquad4.setLowpass(0, 9800, 0.3); // 9800 is the lpf cutoff for the clicks
biquad4.setHighpass(0, 9000, 0.3); // 9000 is the hpf cutoff for the clicks - GETS RANDOMISED LATER!
mixer2.gain(1,0.5);
// noise generator
pink1.amplitude(0.2); // initial volume of pink noise, probably best not to change. it has other volume controls elsewhere
biquad2.setLowpass(0,5000,0.5); // 8800 is the lpf cutoff for the pink noise
biquad2.setHighpass(1,5500,0.5); // 8800 is the hpf cutoff for the pink noise
//mixer1.gain(2,0.2);
// stereo
pink2.amplitude(0.2); // initial volume of pink noise, probably best not to change. it has other volume controls elsewhere
biquad5.setLowpass(0,5000,0.5); // 8800 is the lpf cutoff for the pink noise
biquad5.setHighpass(1,5500,0.5); // 8800 is the hpf cutoff for the pink noise
// guitar signal filtering
biquad3.setHighpass(0, 1100, 1); // 3500 is the hpf cutoff for the guitar signal
biquad3.setLowpass(1, 2000, 1.6); // 7500 is the lpf cutoff for the guitar signal
mixer1.gain(0, 3); // guitar signal gets a volume boost here to compensate for the extreme hpf
// stereo
biquad6.setHighpass(0, 1100, 1); // 3500 is the hpf cutoff for the guitar signal
biquad6.setLowpass(1, 2000, 1.6); // 7500 is the lpf cutoff for the guitar signal
mixer2.gain(0, 3); // guitar signal gets a volume boost here to compensate for the extreme hpf
}
void loop() {
// noise volumes
noisevol = (float) analogRead(A2) / 10000; // increasing 10000 will reduce max pink noise vol
clickvol = (float) analogRead(A3) / 18000; // increasing 16000 will reduce max click vol
mixer1.gain(2,noisevol);
mixer1.gain(1,clickvol);
// stereo
mixer2.gain(2,noisevol);
mixer2.gain(1,clickvol);
// randomise volume amd hpf of clicks
if(!skipping && (millis() - currentMillis >= randomtime))
{
mixer1.gain(1, (float) (rand() % 100) / 500 ); // increase 500 for quieter clicks on average
mixer2.gain(1, (float) (rand() % 100) / 500 ); // increase 500 for quieter clicks on average
clickhpf = (rand() % 5000) + 5000; // generate new random click hpf cutoff (7000hz - 9000hz)
biquad1.setHighpass(0, clickhpf, 0.3); // set the new hpf cutoff for the clicks
biquad4.setHighpass(0, clickhpf, 0.3); // set the new hpf cutoff for the clicks
randomtime = rand() % 50; // increase 50 to increase the maximum number of milliseconds until the next volume and hpf change (time period is randomised)
currentMillis = millis(); // reset timer
}
// vibrato
if(!skipping) {
rate = analogRead(A0) / 600; // read pot A0 to get vibrato rate
depth = analogRead(A1) / 400; // read pot A1 to get vibrato depth
vibrato1.modulation(rate, depth); // set vibrato rate and depth
vibrato2.modulation(rate, depth); // set vibrato rate and depth
}
// toggle switch selects between different frequency bands for the guitar signal
checkToggle ();
if(left){
biquad3.setHighpass(0, 20, 1); // 3500 is the hpf cutoff for the guitar signal
biquad3.setLowpass(1, 1500, 1); // 7500 is the lpf cutoff for the guitar signal
mixer1.gain(0, 2); // guitar signal gets a volume boost here to compensate for the extreme hpf
// stereo
biquad6.setHighpass(0, 20, 1); // 3500 is the hpf cutoff for the guitar signal
biquad6.setLowpass(1, 1500, 1); // 7500 is the lpf cutoff for the guitar signal
mixer2.gain(0, 2); // guitar signal gets a volume boost here to compensate for the extreme hpf
}
else if(middle){
biquad3.setHighpass(0, 1100, 1); // 3500 is the hpf cutoff for the guitar signal
biquad3.setLowpass(1, 2000, 1.2); // 7500 is the lpf cutoff for the guitar signal
mixer1.gain(0, 3); // guitar signal gets a volume boost here to compensate for the extreme hpf
// stereo
biquad6.setHighpass(0, 1100, 1); // 3500 is the hpf cutoff for the guitar signal
biquad6.setLowpass(1, 2000, 1.2); // 7500 is the lpf cutoff for the guitar signal
mixer2.gain(0, 3); // guitar signal gets a volume boost here to compensate for the extreme hpf // 5 = guitar signal
}
else if(right){
biquad3.setHighpass(0, 1800, 1.4); // 3500 is the hpf cutoff for the guitar signal
biquad3.setLowpass(1, 3200, 1.5); // 7500 is the lpf cutoff for the guitar signal
mixer1.gain(0, 3); // guitar signal gets a volume boost here to compensate for the extreme hpf
// stereo
biquad6.setHighpass(0, 1800, 1.4); // 3500 is the hpf cutoff for the guitar signal
biquad6.setLowpass(1, 3200, 1.5); // 7500 is the lpf cutoff for the guitar signal
mixer2.gain(0, 3); // 2 = guitar signal volume
}
// footswitch needle skip
footswitch.update();
if(footswitch.fallingEdge()) { // pre-silence skip-routine
vibrato1.modulation(5, 10); // set specific vibrato rate and depth (5hz, so half an LFO cycle will be 100ms ... and max depth)
currentMillis = millis(); // reset timer
skipping = 1;
}
if(skipping == 1 && (millis() - currentMillis >= 100)) { // wait for vibrato pitch bend to peak... need to be able to reset vibrato phase
while(!digitalRead(0)) { // silence skip-routine, while footswitch held
mixer1.gain(0,0); // mute output
mixer1.gain(1,0);
mixer1.gain(2,0);
}
mixer1.gain(0,10); // turn output volume back up
mixer1.gain(1,0.5);
mixer1.gain(2,0.2);
skipping = 2; // enter post silence mode
currentMillis = millis(); // reset timer
}
if(skipping == 2 && (millis() - currentMillis >= 100)) { // post-silence skip-routine
vibrato1.modulation(rate, depth*3); // set vibrato rate and depth back to pot settings
skipping = 0; // exit skipping routine, return to normal
}
}
Comments