Pattern Effects Implementation - FamiTracker

# FamiTracker

FamiTracker Support Forums

Skip to content

## Pattern Effects Implementation

General discussion about tracking, help and support.
Post Reply

- Print view

2 posts • Page 1 of 1

Kailang
  Posts: 1
  Joined: Wed Jun 29, 2016 1:58 am

### Pattern Effects Implementation

- Quote

Post by Kailang » Wed Jun 29, 2016 2:53 am

I'm a huge fan of chip-tune and real-time music synthesis. And recently I'm trying to recreate the sound of the pulse channel of 2A03 in Unity/C# using a music synthesizing framework written by myself(the framework is independent from Unity, it only uses System.Math for calculations). I've implemented the basic "MOD Effect Sequence"(the Instrument Settings in the Instrument Editor). But I found it is harder to comprehend the inner working of the other "Pattern Effects"(the 0xy ~ Vxx effect set in the effect column).
The core difference from FamiTracker is that I will render the instrument every sample(by calling ISignalProvider.Render()) in system's sample rate(often 48000Hz or 44100Hz), while FamiTracker emulate the actual APU in a way that I currently cannot grasp and use BlipBuffer to downsample the audio buffer.
I emulate the "frame" by advancing a frameCounter every sample and when the frameCounter is larger than 1(a frame has passed), reset the frameCounter to 0 and call AdvanceFrame() to apply the changes.
Since currently I only wrote a parser for MIDI files and I expect the framework to be simple and universal, synthesizers will only deal with NoteOn and NoteOff with information of "channel, note, velocity". So the MOD Effects and Pattern Effects are implemented as parameters of the synthesizer that can be set before ISignalProvider.Init(double sampleRate).
I'll post my thoughts and findings in this single post by updating regularly.
₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪
The 0xy Arpeggio effect is easy, I just setup a table of int[3] and assign:

Code: Select all

`arpeggioTable[1] = ArpeggioEffect.Value >> 4;
arpeggioTable[2] = ArpeggioEffect.Value & 0x0F;
`
For each frame, if 0xy Arpeggio is set, the shifted note is calculated:

Code: Select all

`currentNote += arpeggioTable[arpeggioCounter];
if (++arpeggioCounter >= 3) arpeggioCounter = 0;
`
₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪
The 1xx 2xx Axy Pitch / Volume Slide effect is easy. For each frame I just do:

Code: Select all

`currentVolumn += volumnSlide;
volumnSlide += VolumeSlideEffect.Value;
currentPitch += pitchSlide;
pitchSlide += PitchSlideEffect.Value;
`
₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪
Since I'm making the 2A03Pulse a polyphonic instrument, I'll skip 3xx Portamento for now.
₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪
The 4xy Vibrato and 7xy Tremolo are trickier to deal with. First, they use the same loop-up table, VibratoTable:

Code: Select all

`// The depth of each vibrato level
const double CSoundGen::NEW_VIBRATO_DEPTH[] = {
1.0, 1.5, 2.5, 4.0, 5.0, 7.0, 10.0, 12.0, 14.0, 17.0, 22.0, 30.0, 44.0, 64.0, 96.0, 128.0
};
const double CSoundGen::OLD_VIBRATO_DEPTH[] = {
1.0, 1.0, 2.0, 3.0, 4.0, 7.0, 8.0, 15.0, 16.0, 31.0, 32.0, 63.0, 64.0, 127.0, 128.0, 255.0
};
void CSoundGen::GenerateVibratoTable(int Type)
{
for (int i = 0; i < 16; ++i) { // depth
for (int j = 0; j < 16; ++j) { // phase
int value = 0;
double angle = (double(j) / 16.0) * (3.1415 / 2.0);
if (Type == VIBRATO_NEW)
value = int(sin(angle) * NEW_VIBRATO_DEPTH[i] /*+ 0.5f*/);
else {
value = (int)((double(j * OLD_VIBRATO_DEPTH[i]) / 16.0) + 1);
}
m_iVibratoTable[i * 16 + j] = value;
}
}
}
`
Second, the VibratoTable is 1/4 of a complete sine circle, so in GetVibrato() and GetTremolo() the phase of 0~63 is mapped to an index of 0~15. And... Tremolo USES ONLY HALF OF A COMPLETE SINE CIRCLE, while Vibrato uses a full circle(Notice Phase = m_iTremoloPhase >> 1 in the GetTremolo() method).

Code: Select all

`int CChannelHandler::GetVibrato() const
{
// Vibrato offset (4xx)
int VibFreq;
if ((m_iVibratoPhase & 0xF0) == 0x00)
VibFreq = m_pVibratoTable[m_iVibratoDepth + m_iVibratoPhase];
else if ((m_iVibratoPhase & 0xF0) == 0x10)
VibFreq = m_pVibratoTable[m_iVibratoDepth + 15 - (m_iVibratoPhase - 16)];
else if ((m_iVibratoPhase & 0xF0) == 0x20)
VibFreq = -m_pVibratoTable[m_iVibratoDepth + (m_iVibratoPhase - 32)];
else if ((m_iVibratoPhase & 0xF0) == 0x30)
VibFreq = -m_pVibratoTable[m_iVibratoDepth + 15 - (m_iVibratoPhase - 48)];
if (!m_bNewVibratoMode) {
VibFreq += m_pVibratoTable[m_iVibratoDepth + 15] + 1;
VibFreq >>= 1;
}
if (m_bLinearPitch)
VibFreq = (GetPeriod() * VibFreq) / 128;
return VibFreq;
}
int CChannelHandler::GetTremolo() const
{
// Tremolo offset (7xx)
int TremVol = 0;
int Phase = m_iTremoloPhase >> 1;
if ((Phase & 0xF0) == 0x00)
TremVol = m_pVibratoTable[m_iTremoloDepth + Phase];
else if ((Phase & 0xF0) == 0x10)
TremVol = m_pVibratoTable[m_iTremoloDepth + 15 - (Phase - 16)];
return (TremVol >> 1);
}
`
After that, the vibrato is added to the pitch(0~0x7FF), while tremolo is subtracted from volume(0~0xF).
Of course, pitch and volume is clumped to their own ranged.
After knowing every about the implementation of the original program, it's easy to reconstruct the result with simplicity.
First, I calculate the VibratoTable and TremoloTable separately:

Code: Select all

`VibratoTable = new int[0x400];
for (int depth = 0; depth < 16; depth++)
for (int phase = 0; phase < 64; phase++)
VibratoTable[(depth << 6) | phase] = (int)Math.Round(Math.Sin(2 * Math.PI * phase / 64) * VibratoDepthTable[depth]);
TremoloTable = new int[0x400];
for (int depth = 0; depth < 16; depth++)
for (int phase = 0; phase < 64; phase++)
TremoloTable[(depth << 6) | phase] = (int)Math.Round(Math.Sin(2 * Math.PI * phase / 128) * VibratoDepthTable[depth] / 2);
`
Reset the tremoloStep and tremoloDepth or vibratoStep and vibratoDepth when the effect is updated:

Code: Select all

`tremoloStep = TremoloEffect.Value >> 4;
tremoloDepth = (TremoloEffect.Value & 0x0F) << 6;
vibratoStep = VibratoEffect.Value >> 4;
vibratoDepth = (VibratoEffect.Value & 0x0F) << 6;
`
Add vibrato to current pitch, subtract tremolo from current volume before clump the index:

Code: Select all

`currentVolumn -= TremoloTable[tremoloDepth | tremoloCounter];
currentPitch += VibratoTable[vibratoDepth | vibratoCounter];
`
Done!
₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪
The Qxy Rxy Note Up / Down are difficult to combine, so I deal with them separately.
I thought that the "Note" Up and Down are changing the "note", but I was wrong. I should have known that the Qxy and Rxy are dealing with pitch...
When the effect is updated:

Code: Select all

`noteUpSpeed = NoteUpEffect.Value >> 4;
noteUpTarget += NoteUpEffect.Value & 0x0F;
noteDownSpeed = NoteDownEffect.Value >> 4;
noteDownTarget += NoteDownEffect.Value & 0x0F;
`
Then for every frame:

Code: Select all

`currentNote += noteUp;
if (noteUp < noteUpTarget) noteUp += noteUpSpeed;
if (noteUp > noteUpTarget) noteUp = noteUpTarget;
currentNote -= noteDown;
if (noteDown < noteDownTarget) noteDown += noteDownSpeed;
if (noteDown > noteDownTarget) noteDown = noteDownTarget;
`
₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪
The Exx Pxx Vxx effects that directly mod the value are easy:

Code: Select all

`currentVolumn += VolumnEffect.Value;
currentPitch += PitchEffect.Value;
duty = DutyEffect.Value << 3;
`
₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪
That's everything about the pattern effects that I want to implement.
₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪₪
Why in GetTremolo() the Phase is TremoloPhase right shifted 1 bit?

Code: Select all

`int Phase = m_iTremoloPhase >> 1;`
If the TremoloPhase is 0~64, the Phase is 0~32, ((Phase & 0xF0) == 0x00) when 0 <= Phase < 16, ((Phase & 0xF0) == 0x10) when 16 <= Phase < 32...
So Tremolo only uses the half of a complete sine period, and is like "/\", while the Vibrato is like "/\/". Thus the Tremolo only has positive values while Vibrato has both negative and positive values...
That's indeed very strange...
In CalculateVolume():

Code: Select all

`int CChannelHandler::CalculateVolume() const
{
// Volume calculation
int Volume = m_iVolume >> VOL_COLUMN_SHIFT;
Volume = (m_iSeqVolume * Volume) / 15 - GetTremolo();
Volume = std::max(Volume, 0);
Volume = std::min(Volume, m_iMaxVolume);
if (m_iSeqVolume > 0 && m_iVolume > 0 && Volume == 0)
Volume = 1;
if (!m_bGate)
Volume = 0;
return Volume;
}
`
m_iVolume is
value_in_the_volume_column << VOL_COLUMN_SHIFT
So Volume here is just the value in the volume column. From the UpdateVolumeSlide(), I guess VOL_COLUMN_SHIFT is used to offer finer control over the volume(but however the volume has only 16 steps, 0x0~0xF in the APU register).
I guess SeqVolume / 15 is around 1 to mod the Volume(Volume = m_iSeqVolume / 15 * Volume - GetTremolo();)
So the Volume is directly subtracted by Tremolo which is... 0 to 128???????
No no no, Volume = 16 - 128 doesn't make sense...
...Maybe I'm right, since the 7xy is rarely used when y is high as mentioned by HertzDevil here:
http://forums.famitracker.com/viewtopic.php?f=6&p=4603
Last edited by Kailang on Fri Jul 01, 2016 3:36 am, edited 4 times in total.
Top

Stratelier
  Posts: 378
  Joined: Sun Apr 26, 2015 7:46 pm

### Re: Pattern Effects Implementation

- Quote

Post by Stratelier » Wed Jun 29, 2016 9:56 pm

Yeah, the current tremelo implementation is pretty screwed up.
Top

Display posts from previous:
Sort by

Post Reply

- Print view

2 posts • Page 1 of 1

Return to “General Talk”

Jump to

- Rules
- Wiki
- FamiTracker
- General Talk
- Bug Reports
- Feature Requests
- Source and development
- Music
- Original Music
- Cover Music
- Other
- Site Issues & Suggestions