Page 1 of 4
General guidelines on working with PSG/YM2612 natively
Posted: Sat Oct 04, 2014 1:32 am
by Count SymphoniC
Hello guys. I finally got my native tracker in a state where I can begin basic sound tests (note editing is finally implemented), and I have to admit I'm really excited about it! In this short two weeks, I've learned how to read and code in 68k assembly (still lots to learn!), work with the VDP, bit manipulation, design a gui, draw a cursor that displays and edits values stored in arrays, FIX BUGS, and so on...
Now the sound chips are new to me. But I intend to keep my nagging for answers to a minimum, I understand that fixing a beginner's problems can probably get old for everyone and I really want to learn as much as I can on my own, so in this topic I'm looking for general tips and guidelines from those who are more expert in this.
What are some common pitfalls and things I need to be aware of? Is working with the sound chips really that much different from working with the video display processor(besides the obvious)? I have the Genesis sound software manual and I already understand on a basic level how this kinda works (VDP was good practice). I just want to get some tips and viewpoints here.
Thanks again guys for your help.
Posted: Sat Oct 04, 2014 1:43 am
by Jazzmarazz
I assume that you can write a ton of subroutines that you can jump to when needed. Subroutines such as:
"FM1vol:"
"FM3alg:"
etc.
I can't say for sure, but it really should be as simple (or complex) as talking to any other chip on board. I suggest reading this several times through:
http://www.smspower.org/maxim/Documents/YM2612
and this:
http://www.smspower.org/Development/SN76489
Posted: Sat Oct 04, 2014 1:54 am
by Count SymphoniC
Whoa, these are good links. Thanks!
Edit: It looks like the YM link is from the Genesis manual. But maybe there's more that I'm not seeing.
Posted: Sat Oct 04, 2014 1:57 am
by Jazzmarazz
The only set back is that these articles look at the chips from more of a hardware standpoint. you should have no problem manipulating sound if you obsess over the exact locations of each sound register.
Posted: Sat Oct 04, 2014 1:59 am
by Count SymphoniC
Jazzmarazz wrote:The only set back is that these articles look at the chips from more of a hardware standpoint. you should have no problem manipulating sound if you obsess over the exact locations of each sound register.
Yes, well defined globals should do the trick.
Posted: Sat Oct 04, 2014 2:07 am
by Jazzmarazz
Count SymphoniC wrote:Jazzmarazz wrote:The only set back is that these articles look at the chips from more of a hardware standpoint. you should have no problem manipulating sound if you obsess over the exact locations of each sound register.
Yes, well defined globals should do the trick.
YUP! Define your global pointers and update registers on the fly.
Are there set and reset op codes for individual bits?
psudo-code, do not quote me:
Code: Select all
EnableSoundFM1:
LDA FM1reg
SET bit7, A
STA FM1reg
DisableSoundFM1:
LDA FM1reg
RES bit7, A
STA FM1reg
Anyhow, I don't need to tell you that (especially since idk first hand HAHa...).
Posted: Sat Oct 04, 2014 2:41 am
by Count SymphoniC
Basically you're working with bytes, words and long words. There are some bit operations, boolean algebra... but for setting and resetting specific bits like that, other than the boolean opcodes and rotating bits I'm still learning the 68k instruction set.
Posted: Sat Oct 04, 2014 2:59 am
by Jazzmarazz
Count SymphoniC wrote:Basically you're working with bytes, words and long words. There are some bit operations, boolean algebra... but for setting and resetting specific bits like that, other than the boolean opcodes and rotating bits I'm still learning the 68k instruction set.
Well I know you can pack 1's and 0's with AND and OR logic if you needed so I guess I answered my own question.
What I meant to get at was that many of the sound registers are shared with many individual actions. take for example the gameboy:
FF26 -- SNDREG52 [RW] Sound ON/OFF
Bit7 All sound on[1]/off[0]
Bit3 Sound 4 on[1]/off[0]
Bit2 Sound 3 on[1]/off[0]
Bit1 Sound 2 on[1]/off[0]
Bit0 Sound 1 on[1]/off[0]
The YM and PSG will share similar registers, but you will notice that each bit within the byte located at $FF26 does a different thing. Some register do use the whole byte as a value from 0-255, but more often than not it is just used as a single bit to toggle a parameter.
Posted: Sat Oct 04, 2014 3:19 am
by Count SymphoniC
Yeah I understand. It's become clear to me that I'll need to investigate bit manipulation with 68k some more, I know I've only tapped the surface of this programming language.
For every problem, there is an infinite number of solutions.
EDIT: I totally forgot, you can add and subtract in binary. This could easily be used to toggle specific bits.
Posted: Sat Oct 04, 2014 7:17 pm
by neologix
This reminds me I need to post a thread "What I Learned from Porting YM2612 and SN76489 to JavaScript" soon
For some of the practical aspects of sound playback, the following should be useful:
- Remember that Total Level in YM2612 operators and Attenuation in PSG channels is the inverse of "volume;" a value 0 means loudest.
- The YM2612 is marketed as "frequency" modulation synthesis but in practice acts more like "phase" modulation synthesis. You're less manipulating frequencies and more manipulating phase counters. This makes it easier to understand how the operators actually affect each other if you wanted to do a thing like draw the volume envelopes and draw a sample waveform for preview purposes.
- For PSG, even if you do what FamiTracker does and add the ability to define things like "instrument" arpeggios and envelopes and such, you still have to convert those presets to individual events in preview playback, code export, etc. You'd essentially be writing your own sound engine in this case if you don't choose to implement an existing one and export to that format.
- In your code, treat the sound system as a perfect system but expect to need to account for the usual hardware imperfections. To this day, the official volume balance between the PSG and YM on a Genesis is not publicly known, for example.
- For YM, try to be as thorough as possible in your early drafts of implementation (e.g. include things like channel 3 CSM manipulation, channel 6 PCM toggling, and decent SSG register handling as early as possible). It's easier to make too much to start and comment it out later than it is to make too little to start and add things in later.
- If you can, screenshot and disassemble the SPATULA ("YM 2612 Editor by K. Banks (PD)") rom. Its preset editing interface is bulky and crowded, and shows you what NOT to do to have intuitive YM editing. VGM dumping it also resulted in me finding that it sets all the YM registers EVERY NOTE; find a way to avoid redundancies like that in your code export functionality if you can.
Maxim's docs were what started me down my path in the first place, so I would recommend them highly as well
Posted: Sat Oct 04, 2014 8:04 pm
by Count SymphoniC
Thank you for your wisdom and insight. I'll take all of these into account, especially with the YM2612, as it appears to be a pretty complex chip!
I got a tone sounding off on the PSG yesterday which was exciting for a fleeting moment. Then I realized I was going to have to figure out how to read my notation data, and either convert it to PSG reg data or something. The method I came up with was creating a lookup table for each note of the chromatic scale, with it's values ready to be used with the PSG... of course I couldn't find a note table for the PSG, so I'm creating the table by hand, using the formula FDATA=C divided by F*32. FDATA being the frequency data (in hex word) for the chip, C being the PSG clock in hz (integers) and F being the real frequency of the note, I'm a bit irritated with the Frequency part because the real frequency values in hz are decimal numbers, and that won't work too well with hex word values so I'm having to round off these values. I fear I'm going to have to use a tuner once I'm done getting all this working to adjust the results for pitch perfect notes. But I don't see a cheat sheet just lying around so here we go!
Posted: Sun Oct 05, 2014 1:37 am
by neologix
I wrote up a couple of quick functions in JavaScript a while back for converting Hz to FNUM for both the PSG and YM, if you wanted to have that cheat sheet as a web page or something.
Code: Select all
function MIDI_noteToHz(n){
return 440*Math.pow(2,(n-69)/12);
};
function MIDI_HzToNote(hz){
var div = hz/440.0;
return 12*Math.log(div)/Math.log(2);
}
Code: Select all
function SN_fnumToHz(clock, fn){ // 3579545 is a good NTSC clock value
if (fn<=0) throw new RangeError("FnumToHz - cannot divide by <= 0");
var div = 0.0625, reg = 1/((fn&0x3ff)<<1);
return div*reg*clock;
};
function SN_hzToFnum(clock, hz) { // 3579545 is a good NTSC clock value
var div = 0.0625, reg = 0.5;
if (hz<=0) throw new RangeError("hzToFnum - cannot divide by <= 0");
return (div*reg*clock/hz)|0; // x|0 is the same as casting as int
};
Note: the YM_hzToFnum function returns a JS object with 'fnum' and 'block' properties, so keep that in mind if you're converting it to another language for the cheat sheet.
Code: Select all
function YM_fnumToHz(fn,b) { // defaults to 7670448 NTSC clock value, 6 channels, 24 as the divisor
var rs = 1, clock = 7670448, ch = 6, fm = 24;
if (arguments.length>5) fm = arguments[5];
if (arguments.length>4) ch = arguments[4];
if (arguments.length>3) clock = arguments[3];
if (arguments.length>2) rs = arguments[2];
var cl = ch*fm, pre = clock/(rs*cl);
return fn*pre*Math.pow(2,b-21);
};
function YM_hzToFnum(hz) {
var dp = 4, rs = 1, clock = 7670448, ch = 6, fm = 24;
if (arguments.length>5) fm = arguments[5];
if (arguments.length>4) ch = arguments[4];
if (arguments.length>3) clock = arguments[3];
if (arguments.length>2) rs = arguments[2];
if (arguments.length>1) dp = arguments[1];
var cl = ch*fm, pre = clock/(rs*cl), t = Math.pow(10,dp);
var fn = hz*(1<<14)/pre, b = 8;
while (fn<2048&&(((hz*t)|0)/t)!=(((YM_fnumToHz(fn|0,b)*t)|0)/t)&&--b>0) fn *= 2;
fn>>=1;
return {'fnum':fn|0,'block':b};
};
You know what? I've been meaning to put up a proper cheat sheet myself. I'll be back shortly with a link to it.
Posted: Sun Oct 05, 2014 1:41 am
by r57shell
neologix wrote:Code: Select all
function MIDI_noteToHz(n){
return 440*Math.pow(2,(n-69)/12);
};
Wrong assumption that all 12 notes in octave are uniformly distributed.
Posted: Sun Oct 05, 2014 1:51 am
by Count SymphoniC
r57shell wrote:neologix wrote:Code: Select all
function MIDI_noteToHz(n){
return 440*Math.pow(2,(n-69)/12);
};
Wrong assumption that all 12 notes in octave are uniformly distributed.
EDIT: I didn't see the post above this one, so I edited what I said out.
For NTSC 3579545 รท (NoteFreqhz * 32) = PSGREGDATA
This is the equation I used to convert from hz to data, to write the specific note to the PSG register... I wrote it poorly in the post above but I haven't been in school for years. I didn't design this chip, I don't know why they do it like this, but that's how it works. The equation gives the correct note output.
EDIT: And of course you have to move things around to satisfy the PSG. See documentation for details.
Posted: Sun Oct 05, 2014 2:17 am
by neologix
r57shell wrote:neologix wrote:Code: Select all
function MIDI_noteToHz(n){
return 440*Math.pow(2,(n-69)/12);
};
Wrong assumption that all 12 notes in octave are uniformly distributed.
With MIDI almost no one does micro-tuning, so multiplying by 2^(1/12) to get the default frequencies for the 128 MIDI notes for these purposes is safe.
@Count SymphoniC - I've uploaded the cheat sheet to
http://www.luxatom.com/md-fnum.html ; it uses NTSC clocks by default, though that's only because I only spent a couple of minutes scripting the tables.