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

Jon Fields open source algorithm

Wednesday 6 May 2020, by JonFields45

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 recording.

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. I like 8” from the neck/body joint, but a more distant mic placement might make what is a better IR for your usage.

  1. .WAV file at least 60 seconds long. My program only uses the first 60 seconds of the recording.
  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 like open position strumming followed by “E” bar chords going from F to C followed by a little fingerpicking. Play whatever you think best exhibits your playing style and instrument. I’m not sure it really matters. Doug Young’s recording was a fingerstyle tune with occasional strums.

I will send you back three IRs for each recording. If the file you sent me was mgit.wav, then I will be sending you the following files:

  1. jf45irmgit.wav is the full 2048 sample IR.
  2. jf45ir5050mgit.wav is a 2048 sample, 50% IR and 50% pickup, for IR loaders without a mix capability.
  3. jf45ir1024mgit.wav is a 1024 sample version of the 100% IR for IR loaders that do not support a longer IR.
  4. FFTjf45irmgit.jpg is a picture of the 100% IR frequency response

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 60 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!)

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 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, jf45ir.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.

Place your guitar pickup/mic recordings in the same directory as my script jf45ir.m and double clip (run) jf45ir.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)

jf45ir(’mgit’)

Octave will then generate four files, three IRs plus the frequency plot. They will be in the same directory as jf45ir.m and mgit.wav.

How Does jf45ir.m Work?

Reading this section is purely optional!

Unlike ToneDexter or the Baggs Soundlink, 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.

I am using a 44.1 kHz sample rate for my WAV files. The extra frequency content supported by DVD 48 kHz is not needed and the frequency domain calculations get wasted on more frequency bands above the guitar’s range. Cheap IR loaders run at 44.1, in time a fixed 2048 samples is longer at 44.1, and better loaders (such as my HX Stomp) can resample 44.1 to 48 on download. My code generates the IR with the same sample rate as the input WAV file.

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 6 seconds and grab the next segment and repeat the clip/SNR test. I do this up to 8 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. 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 50/50 IR/pickup 2048 sample version.

jf45ir.m

function jf45ir(input)
# jonfields45@gmail.com
# input.wav is the guitar sample, pickup left, mic right
diary logfile;
diary on;
# IR generation:
ss = (2^17); # ~3 second segment size for analysis
nstep = 2; # segment step ~6 seconds
nstart = 3; # index into .wav in segments, ~6 seconds
nlast = 17; # up to 8 segments checked
count = 0; # initialize good segment count
nzcount = 0; # initialize near zero count
accirfftnnz = zeros(ss,1); # initialize no near zero IR
# Blackman window function:
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);
    # load segment from wav file
    [s,fs] = audioread([input,'.wav'],[start, finish]);
    smax = max(max(s));
    clip = (smax > 0.999); # check for clipping
    toolow = (smax < 0.178); # 15 dB down
    # calculate per segment IR
    if (clip == 0 && toolow == 0 && count < 4)
         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)); # -65dB
         for m = 1:1:ss
              # check and fix  near zeros in pickup FFT
              if (abs(pupfft(m)) < nearzero)
                   nzcount = nzcount + 1; # Count near 0s
                   pupfftnnz(m) = 1; # erase near zero
                   micfftnnz(m) = 1; # erase near zero
              end  
         end
         # IR=FFT(mic)/FFT(PUP)
         irfftnnz = micfftnnz ./ pupfftnnz;
         # accumulate IRs
         accirfftnnz = accirfftnnz + irfftnnz;
         count = count + 1; # increment segment count
    end
end
if(count==0)
   ['Zero Segments Processed due to Clip/Min errors']
   return;
end
irnnz = ifft(accirfftnnz/count); # calc IR
ir2048nnz = irnnz(1:2048); # truncate to 2048
avgnzcount = nzcount / (count*2);
['Processed Segments = ',num2str(count)]
['Average Near Zero per Segment = ',num2str(avgnzcount)]
filename = ['jf45ir',input,'.wav'];
audiowrite(filename,ir2048nnz,fs); # write IR
# FFT final IR for frequency plot
fftir2048nnz = fft(ir2048nnz);
# print frequency plot
y = 20*log(abs(fftir2048nnz(2:1024)));
x = fs*(1:1023)/2048;
semilogx(x,y);
xlim([20 15000]);
xlabel('Hz');
filename = ['FFTjf45ir',input,'.jpg'];
ylabel([filename,' dB']);
print(filename,'-djpeg');
filename = ['jf45ir1024',input,'.wav'];
audiowrite(filename,ir2048nnz(1:1024),fs); # write IR
# 50/50 IR generation
irrms=(sum(ir2048nnz.*ir2048nnz))^0.5;
ir2048nnz5050=(ir2048nnz*0.5);
ir2048nnz5050(1)=ir2048nnz5050(1)+(irrms/2);
filename = ['jf45ir5050',input,'.wav'];
audiowrite(filename,ir2048nnz5050,fs); # write IR
close;
diary off;

Portfolio

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

Any message or comments?

Who are you?
Your post
  • To create paragraphs, just leave blank lines.