Home > Generalities > How do I get my own guitar IR? > Jon Fields open source algorithm

Jon Fields open source algorithm

Wednesday 6 May 2020

  • LATEST SCRIPT ONLY REQUIRES A 30 SECOND SAMPLE
  • NOW GENERATES 5 BYPASS BLENDED IR OPTIONS
  • PICKUP LEFT, MIC RIGHT, OPEN POSITION STRUMMING BEST
  • The latest script name is jf45irv30sec.m,
  • The attached screenshots are for the older script jf45ir.m

Check out this very brief demo that shows where you can take a bottom-of-the-line piezo undersaddle transducer with the cheapest of guitar cabinet IR loaders:

https://youtu.be/SELEE4yugjE

Check out this AGF post for Doug Young’s demo of my IR (and Cuki’s)

https://www.acousticguitarforum.com/forums/showpost.php?p=6278203&postcount=12

The Easy Way to Generate a Custom IR for Your Guitar

Email me (jonfields45@gmail.com), a Dropbox or Google Drive link to your recordings.

If your .WAV file is less than 25 MB it can be sent as an email attachment. If you Zip up your recordings you can include several in a less than 25 MB email attachment.

Generating the IR takes only seconds in Octave. Octave is the free open source GNU Matlab clone. Matlab is a math software integrated development environment used in almost every technical field.

If you want, send a few recordings with different mic placements. 8” from the neck/body joint is a good starting point, but I recommend you also try 16" and 24" (send three recordings). You will find IRs generated with more distant miking easier to use at higher stage volumes where closer miking will sound richer in your living room. In the year since this script has been released I’ve concluded simple open position strumming does as good a job as anything you could choose to play. Doug Young’s recording was a fingerstyle tune with occasional strums.

  1. .WAV file at least 30 seconds long. My program only uses the first 30 seconds of the recording, and it needs at least 30 seconds as written.
  2. Pickup left, Mic right, set your DAW for the two to be about equal in volume.
  3. No clipping.
  4. Try to keep your peaks within 15 dB of clipping (decent SNR).
  5. The IRs generated will be at the same sample rate as the recording. If your IR loader only supports 44.1 or 48 kHz, you need to send a file at the appropriate sample rate. Many IR loader PC/Mac programs support resample on download and can handle either.

I will send you back 12 IRs for each recording. Check the file naming convention PDF attached below for more information.

The three most common errors users have encountered are:

  1. To forget to pan the pickup left and mic right when exporting the sample from their DAW.
  2. The sample WAV file is less than 30 seconds.
  3. If running the script themselves (see next section) they forget to enclose the name of the wave file in single quotes, or include the extension instead of just the wave file name.

The Do It Yourself Way (this is not hard at all!)

Using Python
This is a Python transcription of Jon’s code written by Jim Armsden:

Zip - 1.6 kb

Download the Python script and run it with Jupyter Notebook or Google Colab.

Using OCTAVE
Here is the original Octave/Matlab code:

The primary advantage of doing this yourself at home is the ability to add your own EQ to the mic recording before generating the IR. This way you can cook your EQ choices into the IR. Compared to commercial acoustic guitar IR generators, the lack of pre or post processing EQ might be unique to jf45ir.m. In my opinion, those EQ decisions really belong to the end user and not the IR software writer.

  1. Download the Octave version of my IR generator, jf45irv30sec.m, from this website. Scroll to the end of this article to find the download.
  2. Download the free GNU math development environment Octave and follow the installation instructions:

https://www.gnu.org/software/octave/download.html

Some platforms (Mac) seem to lag others. Install the latest not Beta version. Nothing in the new Octave releases is needed for this script. The Octave interface has its roots in Unix from many decades ago. If you are finding its user interface unfamiliar, skip down to the pictures attached to this article. I have found on some MACs the default font in the command window is too small to see the required vertical single quotes. Go to Octave-GUI, preferences, command, and increase the font size if needed.

Place your guitar pickup/mic recordings in the same directory as my latest script jf45irv30sec.m and double clip (run) jf45irv30sec.m. The GUI version of Octave will open in that directory. Alternately, after installing Octave, run the GUI version and change the current directory to the one in which you placed my script and your recordings.

If your recording is called mgit.wav, for example, then type the following in the Octave command window followed by a carriage return (don’t forget to include the single quotes before and after the file name and don’t include the ".wav" extension)

jf45irv30sec(’mgit’)

Octave will then generate 12 IRs plus the frequency plot. They will be in the same directory as jf45irv30sec.m and mgit.wav.

How Does jf45irv30sec.m Work?

Reading this section is purely optional!

Unlike ToneDexter or the Baggs Voiceprint, I am only trying to solve the IR generation problem. You still need a pedal for playback in performance. Since I am not trying to shoehorn IR generation into a pedal, I am taking full advantage of the math power of a modern Intel PC/Mac processor. I am using huge segments of your sample and this is best accomplished with double precision floating point and copious (by stomp box standards, not PC/Mac standards) memory.

I am taking a purely mathematical approach to the problem of coming up with an IR that makes the left channel (pickup) of the sample sound like the right channel (mic). There are no assumptions about the instrument, pickup, or mic being made (aside from the usual linear and time invariant). This very simplified approach seems to work well and does not have hidden optimizations which might interfere with your guitar, pickup, or mic selection.

The script itself should make sense to anyone with two semesters of college calculus and access to Wikipedia to get a basic understanding of a Fourier Transform and a Window Function.

You can think of the system we are analyzing as two parallel IRs. One takes the real guitar and gives you the pickup output, and the other takes the real guitar and creates the mic output. Since both systems are assumed to be linear and time invariant, there should be a third IR that can take you directly from the pickup to the mic. It is that third IR we are attempting to calculate.

First, I extract segments of the recorded pickup and mic. I tried 1, 2, 4, 8, and 10 segments of lengths 2^15, 2^16, 2^17, 2^18, 2^19. Too short a sample sounded not as good as longer, and after a certain point longer was not better. The math of Discrete Fourier Transforms has a quantization noise problem that favors longer segments.

I settled on 4 segments, 2^17 samples (3 seconds) long. I skip 6 seconds into the source wav file and grab a segment. I check it for clipping and having a peak lower than -15 dB down from clipping. The segment is rejected for clipping or low SNR. I then skip ahead 3 seconds and grab the next segment and repeat the clip/SNR test. I do this up to 7 times to yield 4 segments of your sample.

Those of you who are familiar with how clipping distortion creates sharp edges and generates harmonics would probably not be surprised that taking a segment out of a longer sample does something similar at the beginning and end of the sample. A window function takes your segment and slowly raises the volume from zero and back to zero. Cuki uses a Blackman window and I also chose Blackman. The math again prefers a longer segment for less windowing harmonic generation.

Next all the segments get Fast (Discrete) Fourier Transformed (FFT). The mic FFT is divided by the pickup FFT. The four division results are averaged together and then an Inverse Discrete Fourier Transform (IFFT) generates the IR. It is sort of pickup, times mic divided by pickup IR, equals mic...I’m avoiding that convolution word.

Minimum phase is an option mentioned in the ToneDexter patent and I also calculated minimum phase versions. I confirmed with frequency plots that the code works correctly. I could not hear any difference and gave up on that approach. This IR approach does not gain up the bass response and I am thinking without the bass boost of other IRs the minimum phase version is not that important.

My one piece of unique intellectual property (as far as I know) was a “no near zero” criteria in the pickup FFT. Dividing by zero is a bad idea and the IR can’t create something from nothing. I search the pickup FFT for coefficients (frequency bands) where the magnitude is more than 65 dB down from the peak. Those coefficients get replaced with “1” in both the pickup and mic FFTs (the resulting IR has a 0 dB gain for those frequencies). This produces a better sounding result with a better behaved frequency plot (no motivation to add Tone Match to this algorithm). High and low cut filtering often needed by other IR generation techniques are not needed here. Post processing to notch resonant peaks in the lowest few 100 Hz for live performance feedback control is also not required with this algorithm.

To summarize, I am producing the IRs from four 2^17 sample segments, no near zeros with a 65 dB down criteria, no minimum phase transformation, truncated to 2048 and 1024 sample versions, along with a selection of IR/pickup blended IRs.

jf45irV30sec.m

function jf45irv30sec(input)
# jonfields45@gmail.com
# input.wav is the sample to be processed, pickup left, mic right
diary logfile;
diary on;
# IR generation:
ss = (2^17); # ~3 second segment size for analysis
nstep = 1; # segment step ~3 seconds
nstart = 3; # index into .wav in segments, ~6 seconds
nlast = 9; # up to 7 segments checked for suitable SNR & no clip
count = 0; # initialize good segment count
nzcount = 0; # initialize near zero count
accirfftnnz = zeros(ss,1); # initialize no near zero IR accumulator
window = (.42-.5*cos(2*pi*(0:ss-1)/(ss-1))+.08*cos(4*pi*(0:ss-1)/(ss-1)))';
#
for n = nstart:nstep:nlast # process segments
    start = (((n-1) * ss) + 1);
    finish = (n * ss);
    [s,fs] = audioread([input,'.wav'],[start, finish]); # load segment
    smax = max(max(s));
    clip = (smax > 0.999); # check for clipping
    toolow = (smax < 0.178); # more than 15 dB down
    if (clip == 0 && toolow == 0 && count < 4) # per segment IR
         pickup = s(:,1) .* window;
         mic = s(:,2) .* window;
         pupfft = fft(pickup);
         micfft = fft(mic);
         pupfftnnz = pupfft;
         micfftnnz = micfft;
         nearzero = 10^(-65/20)*abs(max(pupfft)); # ~0 is -65dB from peak
         for m = 1:1:ss
              if (abs(pupfft(m)) < nearzero) # Check near 0 div
                   nzcount = nzcount + 1; # Count near 0s (pos & neg freq)
                   pupfftnnz(m) = 1; # erase near zero
                   micfftnnz(m) = 1; # erase near zero
              end  
         end
         irfftnnz = micfftnnz ./ pupfftnnz; # segment IR=FFT(mic)/FFT(PUP)
         accirfftnnz = accirfftnnz + irfftnnz; # accumulate segment IRs
         count = count + 1; # increment processed segment count
    end
end
if(count==0)
   ['Zero Segments Processed due to Clip/Min errors']
   return;
end
#
irnnz = ifft(accirfftnnz/count); # calc average IR over processed segments
ir2048nnz = irnnz(1:2048); # truncate to 2048
#
avgnzcount = nzcount / (count*2); # pos freq avg near zero count per seg
['Processed Segments = ',num2str(count)]
['Average Near Zero per Segment = ',num2str(avgnzcount)]
['Sample Rate = ',num2str(fs)]
#
filename = ['jf45ir ',input,'.wav'];
audiowrite(filename,ir2048nnz,fs); # write IR
#
filename = ['jf45ir1024 ',input,'.wav'];
audiowrite(filename,ir2048nnz(1:1024),fs); # write IR
#
fftir2048nnz = fft(ir2048nnz); # FFT final IR for frequency plot
y = 20*log(abs(fftir2048nnz(2:1024))); # print frequency plot
x = fs*(1:1023)/2048;
semilogx(x,y);
xlim([20 15000]);
xlabel('Hz');
filename = ['FFTjf45ir ',input,'.jpg'];
ylabel([filename,' dB']);
print(filename,'-djpeg');
#
# 50/50 IR generation
irrms=(sum(ir2048nnz.*ir2048nnz))^0.5;
ir2048nnz5050=(ir2048nnz*0.5);
ir2048nnz5050(1)=ir2048nnz5050(1)+(irrms*0.5);
#
filename = ['jf45ir5050 ',input,'.wav'];
audiowrite(filename,ir2048nnz5050,fs); # write IR
#
filename = ['jf45ir50501024 ',input,'.wav'];
audiowrite(filename,ir2048nnz5050(1:1024),fs); # write IR
#
# 75/25
ir2048nnz7525=(ir2048nnz*0.75);
ir2048nnz7525(1)=ir2048nnz7525(1)+(irrms*0.25);
#
filename = ['jf45ir7525 ',input,'.wav'];
audiowrite(filename,ir2048nnz7525,fs); # write IR
#
filename = ['jf45ir75251024 ',input,'.wav'];
audiowrite(filename,ir2048nnz7525(1:1024),fs); # write IR
#
# 60/40
ir2048nnz6040=(ir2048nnz*0.6);
ir2048nnz6040(1)=ir2048nnz6040(1)+(irrms*0.4);
#
filename = ['jf45ir6040 ',input,'.wav'];
audiowrite(filename,ir2048nnz6040,fs); # write IR
#
filename = ['jf45ir60401024 ',input,'.wav'];
audiowrite(filename,ir2048nnz6040(1:1024),fs); # write IR
# 40/60
ir2048nnz4060=(ir2048nnz*0.4);
ir2048nnz4060(1)=ir2048nnz4060(1)+(irrms*0.6);
#
filename = ['jf45ir4060 ',input,'.wav'];
audiowrite(filename,ir2048nnz4060,fs); # write IR
#
filename = ['jf45ir40601024 ',input,'.wav'];
audiowrite(filename,ir2048nnz4060(1:1024),fs); # write IR
# 25/75
ir2048nnz2575=(ir2048nnz*0.25);
ir2048nnz2575(1)=ir2048nnz2575(1)+(irrms*0.75);
#
filename = ['jf45ir2575 ',input,'.wav'];
audiowrite(filename,ir2048nnz2575,fs); # write IR
#
filename = ['jf45ir25751024 ',input,'.wav'];
audiowrite(filename,ir2048nnz2575(1:1024),fs); # write IR
#
close;
diary off;
If you like this website, please donate to support it.

Portfolio

  • (1) Start Octave GUI
  • (2 & 3) Change Directories
  • C:\Users\jonfi\Google Drive\jf45irdemo
  • (4 & 5) Type jf45ir('mgit')