commit b466a624876729b98b5a9384e1ce40cb479f644e
parent d12801ac7d7dfc0e22820e112586004211af73ae
Author: jatinchowdhury18 <jatinchowdhury18@users.noreply.github.com>
Date: Fri, 15 Feb 2019 00:48:56 -0800
Implement tape loss effects
Diffstat:
11 files changed, 240 insertions(+), 4 deletions(-)
diff --git a/Paper/420_paper.pdf b/Paper/420_paper.pdf
Binary files differ.
diff --git a/Paper/420_paper.tex b/Paper/420_paper.tex
@@ -242,7 +242,14 @@ described as follows \cite{Kadis}:
\end{equation}
%
for sinusoidal input, where $k$ = wave number, $d$ is the distance between the tape and the playhead,
-$g$ is the gap width of the play head, and $\delta$ is the thickness of the tape.
+$g$ is the gap width of the play head, and $\delta$ is the thickness of the tape. The wave number
+is given by:
+
+\begin{equation}
+ k = \frac {2 \pi f}{v}
+\end{equation}
+%
+where $f$ is the frequency and $v$ is the tape speed.
\section{Digitizing the System}
\subsection{Record Head}
@@ -332,7 +339,7 @@ meter, the following plot shows the Magnetisation output.
\begin{figure}[ht]
\center
\includegraphics[width=3in]{../Simulations/Sim2-M_H.png}
- \caption{\label{flowchart}{\it Digitized Hysteresis Loop Simulation}}
+ \caption{\label{HysteresisSim}{\it Digitized Hysteresis Loop Simulation}}
\end{figure}
%
\subsection{Play Head}
@@ -346,8 +353,23 @@ or,
\hat{V}(n) = NWEv \mu_0 g \hat{M}(t)
\end{equation}
%
-The loss effects described in \cref{eq:lossEffects}
-can be modelled as a series of digital filters.
+\subsubsection{Loss Effects}
+In the real-time system, we model the playhead
+loss effects with an FIR filter, derived by
+taking the inverse DFT of the
+loss effects described in \cref{eq:lossEffects}.
+It is worth noting once again that the loss effects
+and therefore the FIR filter as well, are dependent
+on the tape speed.
+The following plot shows the frequency response of
+the loss effects for tape-head spacing of 20 microns,
+head gap width of 5 microns, tape thickness of 35 microns,
+and tape speed of 15 ips.
+\begin{figure}[ht]
+ \center
+ \includegraphics[width=3in]{../Simulations/Loss_Effects.png}
+ \caption{\label{lossEffectsSim}{\it Frequency Response of Playhead Loss Effects}}
+\end{figure}
\subsection{Oversampling}
To reduce frequency aliasing caused by the hysteresis non-linearity,
diff --git a/Plugin/CHOWTapeModel.jucer b/Plugin/CHOWTapeModel.jucer
@@ -8,6 +8,14 @@
<FILE id="mQFlPP" name="MyLNF.h" compile="0" resource="0" file="Source/GUI Extras/MyLNF.h"/>
</GROUP>
<GROUP id="{D2422983-A0E9-6A14-2092-2381CB1F3E7F}" name="Processors">
+ <GROUP id="{99069F32-1399-FF98-7C4A-19F8E855C2AA}" name="Loss Effects">
+ <FILE id="aEdcW7" name="LossEffects.cpp" compile="1" resource="0" file="Source/Processors/Loss Effects/LossEffects.cpp"/>
+ <FILE id="TFnfhT" name="LossEffects.h" compile="0" resource="0" file="Source/Processors/Loss Effects/LossEffects.h"/>
+ <FILE id="MSDyaq" name="LossEffectsFilter.cpp" compile="1" resource="0"
+ file="Source/Processors/Loss Effects/LossEffectsFilter.cpp"/>
+ <FILE id="C8T3dH" name="LossEffectsFilter.h" compile="0" resource="0"
+ file="Source/Processors/Loss Effects/LossEffectsFilter.h"/>
+ </GROUP>
<GROUP id="{E3FB6B88-F1C2-FAF4-BE48-F8C36FEC858A}" name="Speed Filters">
<FILE id="rJ6Je0" name="SpeedFilterProcessor.cpp" compile="1" resource="0"
file="Source/Processors/Speed Filters/SpeedFilterProcessor.cpp"/>
diff --git a/Plugin/Source/PluginProcessor.cpp b/Plugin/Source/PluginProcessor.cpp
@@ -29,6 +29,7 @@ ChowtapeModelAudioProcessor::ChowtapeModelAudioProcessor()
tapeSpeed->addListener (this);
speedFilter.setSpeed (*tapeSpeed);
+ lossEffects.setSpeed (*tapeSpeed);
hysteresis.setOverSamplingFactor (*overSampling);
}
@@ -45,7 +46,10 @@ void ChowtapeModelAudioProcessor::parameterValueChanged (int paramIndex, float n
else if (paramIndex == overSampling->getParameterIndex())
hysteresis.setOverSamplingFactor (*overSampling);
else if (paramIndex == tapeSpeed->getParameterIndex())
+ {
speedFilter.setSpeed (*tapeSpeed);
+ lossEffects.setSpeed (*tapeSpeed);
+ }
}
//==============================================================================
@@ -116,6 +120,7 @@ void ChowtapeModelAudioProcessor::prepareToPlay (double sampleRate, int samplesP
inGainProc.prepareToPlay (sampleRate, samplesPerBlock);
speedFilter.prepareToPlay (sampleRate, samplesPerBlock);
hysteresis.prepareToPlay (sampleRate, samplesPerBlock);
+ lossEffects.prepareToPlay (sampleRate, samplesPerBlock);
outGainProc.prepareToPlay (sampleRate, samplesPerBlock);
}
@@ -124,6 +129,7 @@ void ChowtapeModelAudioProcessor::releaseResources()
inGainProc.releaseResources();
speedFilter.releaseResources();
hysteresis.releaseResources();
+ lossEffects.releaseResources();
outGainProc.releaseResources();
}
@@ -158,6 +164,8 @@ void ChowtapeModelAudioProcessor::processBlock (AudioBuffer<float>& buffer, Midi
inGainProc.processBlock (buffer, midiMessages);
hysteresis.processBlock (buffer, midiMessages);
+
+ lossEffects.processBlock (buffer, midiMessages);
speedFilter.processBlock (buffer, midiMessages);
outGainProc.processBlock (buffer, midiMessages);
diff --git a/Plugin/Source/PluginProcessor.h b/Plugin/Source/PluginProcessor.h
@@ -4,6 +4,7 @@
#include "Processors/Hysteresis/HysteresisProcessor.h"
#include "Processors/GainProcessor.h"
#include "Processors/Speed Filters/SpeedFilterProcessor.h"
+#include "Processors/Loss Effects/LossEffectsFilter.h"
//==============================================================================
/**
@@ -60,6 +61,7 @@ public:
private:
SpeedFilterProcessor speedFilter;
HysteresisProcessor hysteresis;
+ LossEffectsFilter lossEffects;
GainProcessor inGainProc;
GainProcessor outGainProc;
diff --git a/Plugin/Source/Processors/Loss Effects/LossEffects.cpp b/Plugin/Source/Processors/Loss Effects/LossEffects.cpp
@@ -0,0 +1,53 @@
+#include "LossEffects.h"
+
+LossEffects::LossEffects()
+{}
+
+void LossEffects::init (float sampleRate, float speed)
+{
+ const auto numBins = sampleRate / (float) order;
+ const auto binWidth = sampleRate / numBins;
+ const auto speedMetric = speed * inchesToMeters;
+
+ // Set freq domain multipliers
+ float H[order];
+ for (int k = 0; k < order / 2; k++)
+ {
+ const auto freq = ((float) k * binWidth) + (binWidth / 2.0f);
+ const auto waveNumber = MathConstants<float>::twoPi * freq / speed;
+
+ const auto spacingLoss = std::expf (-1.0f * waveNumber * spacing);
+ const auto gapLoss = std::sinf (waveNumber * gap / 2.0f) / (waveNumber * gap / 2.0f);
+ const auto thicknessLoss = (1.0f - std::expf (-waveNumber * thickness)) / (waveNumber * thickness);
+
+ H[k] = spacingLoss * gapLoss * thicknessLoss;
+ H[order - k - 1] = H[k];
+ }
+
+ // Create time domain filter signals
+ for (int n = 0; n < order; n++)
+ {
+ h[n] = 0;
+ for (int k = 0; k < order; k++)
+ h[n] += H[k] * std::cosf (MathConstants<float>::twoPi * (float) k * (float) n / (float) order);
+
+ h[n] /= (float) order;
+ }
+
+ // Clear xs
+ xPtr = 0;
+ for (int n = 0; n < order; n++)
+ x[n] = 0;
+}
+
+float LossEffects::process (float in)
+{
+ float y = 0.0f;
+ x[xPtr]= in;
+ for (int n = 0; n < order; n++)
+ y += h[n] * x[negativeAwareModulo<int> (xPtr - n, order)];
+
+ xPtr = (xPtr + 1) % order;
+
+ return y;
+}
diff --git a/Plugin/Source/Processors/Loss Effects/LossEffects.h b/Plugin/Source/Processors/Loss Effects/LossEffects.h
@@ -0,0 +1,35 @@
+#ifndef LOSSEFFECTS_H_INCLUDED
+#define LOSSEFFECTS_H_INCLUDED
+
+#include "JuceHeader.h"
+
+namespace
+{
+ constexpr float spacing = (float) 20e-6;
+ constexpr float gap = (float) 5e-6;
+ constexpr float thickness = (float) 35e-6;
+ constexpr float inchesToMeters = 0.0254f;
+
+ enum
+ {
+ order = 100,
+ };
+}
+
+class LossEffects
+{
+public:
+ LossEffects();
+
+ void init (float sampleRate, float speed);
+ float process (float in);
+
+private:
+ float h[order];
+ float x[order];
+ int xPtr = 0;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LossEffects)
+};
+
+#endif //LOSSEFFECTS_H_INCLUDED
diff --git a/Plugin/Source/Processors/Loss Effects/LossEffectsFilter.cpp b/Plugin/Source/Processors/Loss Effects/LossEffectsFilter.cpp
@@ -0,0 +1,37 @@
+#include "LossEffectsFilter.h"
+
+void LossEffectsFilter::prepareToPlay (double sampleRate, int samplesPerBlock)
+{
+ setRateAndBufferSizeDetails (sampleRate, samplesPerBlock);
+
+ lossProcessors[0].init ((float) sampleRate, speed);
+ lossProcessors[1].init ((float) sampleRate, speed);
+}
+
+void LossEffectsFilter::releaseResources()
+{
+}
+
+void LossEffectsFilter::processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midiBuffer*/)
+{
+ for (int channel = 0; channel < buffer.getNumChannels(); ++channel)
+ {
+ auto* x = buffer.getWritePointer (channel);
+ for (int n = 0; n < buffer.getNumSamples(); n++)
+ {
+ x[n] = lossProcessors[channel].process (x[n]);
+ }
+ }
+}
+
+void LossEffectsFilter::setSpeed (String newSpeed)
+{
+ if (newSpeed == "3.75 ips")
+ speed = 3.75f;
+ else if (newSpeed == "7.5 ips")
+ speed = 7.5f;
+ else if (newSpeed == "15 ips")
+ speed = 15.0f;
+
+ prepareToPlay (getSampleRate(), getBlockSize());
+}
diff --git a/Plugin/Source/Processors/Loss Effects/LossEffectsFilter.h b/Plugin/Source/Processors/Loss Effects/LossEffectsFilter.h
@@ -0,0 +1,25 @@
+#ifndef LOSSEFFECTSFILTER_H_INCLUDED
+#define LOSSEFFECTSFILTER_H_INCLUDED
+
+#include "LossEffects.h"
+#include "../ProcessorBase.h"
+
+class LossEffectsFilter : public ProcessorBase
+{
+public:
+ LossEffectsFilter() : ProcessorBase ("Loss Effects Filter") {}
+
+ void prepareToPlay (double sampleRate, int samplesPerBlock) override;
+ void releaseResources() override;
+ void processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midiBuffer*/) override;
+
+ void setSpeed (String newSpeed);
+
+private:
+ LossEffects lossProcessors[2];
+ float speed = 15.0f;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LossEffectsFilter)
+};
+
+#endif //LOSSEFFECTSFILTER_H_INCLUDED
diff --git a/Simulations/Loss_Effects.png b/Simulations/Loss_Effects.png
Binary files differ.
diff --git a/Simulations/Loss_Effects.py b/Simulations/Loss_Effects.py
@@ -0,0 +1,46 @@
+import numpy as np
+import matplotlib.pyplot as plt
+import scipy.signal as signal
+
+# Constants
+N = 100
+d = 20e-6 #Spacing between tape and head
+g = 5e-6 #Head gap width
+delta = 35e-6 #Tape thickness
+v = 15 * 0.0254
+
+#f = np.linspace (0, 24000, N/2)
+f = np.linspace (0, 48000, N)
+n = np.linspace (0, N, N)
+#n = np.linspace (0, N/2, N/2)
+
+# Calculate H(f)
+H = np.zeros(N)
+H[0] = 1
+waveNum = 2 * np.pi * f[1:int (N/2)] / v
+#H[1:int (N/2)] = np.e ** (- abs(waveNum) * d) # Spacing loss
+#H[1:int (N/2)] = np.sin (waveNum * g / 2) / (waveNum * g / 2) #gap loss
+#H[1:int (N/2)] = (1 - np.exp (-waveNum * delta))/(waveNum * delta) #Thickness loss
+H[1:int (N/2)] = (np.e ** (- abs(waveNum) * d)) * (np.sin (waveNum * g / 2) / (waveNum * g / 2)) * ((1 - np.exp (-waveNum * delta))/(waveNum * delta))
+H_flip = np.flip (H[0:int (N/2)], 0)
+H[int (N/2):N] = H_flip
+
+# "Roll your own" iDFT
+h = np.zeros (N)
+for n_k in range (N):
+ for k in range (N):
+ h[n_k] += H[k] * np.cos (2 * np.pi * k * n_k / N)
+ h[n_k] *= (1/N)
+
+#h = np.fft.ifft (H)
+H = np.fft.fft (h)
+w, H_t = signal.freqz (h)
+
+# Plotting output
+#plt.plot(n, h)
+#plt.plot (w * 22000 / np.pi, abs (H_t))
+plt.semilogx (f[0:int (N/2)], 20 * np.log10 (H[0:int (N/2)]))
+plt.title ("Tape Loss Effects vs. Frequency")
+plt.xlabel ("Frequency [Hz]")
+plt.ylabel ("Amplitude [dB]")
+plt.show()