General guidelines on working with PSG/YM2612 natively

For anything related to sound (YM2612, PSG, Z80, PCM...)

Moderator: BigEvilCorporation

Count SymphoniC
Very interested
Posts: 149
Joined: Sat Nov 17, 2012 3:58 am

General guidelines on working with PSG/YM2612 natively

Post by Count SymphoniC » Sat Oct 04, 2014 1:32 am

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... :P

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.

Jazzmarazz
Very interested
Posts: 60
Joined: Wed Mar 12, 2014 11:11 pm
Location: Michigan
Contact:

Post by Jazzmarazz » Sat Oct 04, 2014 1:43 am

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

Count SymphoniC
Very interested
Posts: 149
Joined: Sat Nov 17, 2012 3:58 am

Post by Count SymphoniC » Sat Oct 04, 2014 1:54 am

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.
Last edited by Count SymphoniC on Sat Oct 04, 2014 1:58 am, edited 1 time in total.

Jazzmarazz
Very interested
Posts: 60
Joined: Wed Mar 12, 2014 11:11 pm
Location: Michigan
Contact:

Post by Jazzmarazz » Sat Oct 04, 2014 1:57 am

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. :P

Count SymphoniC
Very interested
Posts: 149
Joined: Sat Nov 17, 2012 3:58 am

Post by Count SymphoniC » Sat Oct 04, 2014 1:59 am

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. :P
Yes, well defined globals should do the trick.

Jazzmarazz
Very interested
Posts: 60
Joined: Wed Mar 12, 2014 11:11 pm
Location: Michigan
Contact:

Post by Jazzmarazz » Sat Oct 04, 2014 2:07 am

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. :P
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...).

Count SymphoniC
Very interested
Posts: 149
Joined: Sat Nov 17, 2012 3:58 am

Post by Count SymphoniC » Sat Oct 04, 2014 2:41 am

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.

Jazzmarazz
Very interested
Posts: 60
Joined: Wed Mar 12, 2014 11:11 pm
Location: Michigan
Contact:

Post by Jazzmarazz » Sat Oct 04, 2014 2:59 am

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.

Count SymphoniC
Very interested
Posts: 149
Joined: Sat Nov 17, 2012 3:58 am

Post by Count SymphoniC » Sat Oct 04, 2014 3:19 am

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.

neologix
Very interested
Posts: 122
Joined: Mon May 07, 2007 5:19 pm
Location: New York, NY, USA
Contact:

Post by neologix » Sat Oct 04, 2014 7:17 pm

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 :)

Count SymphoniC
Very interested
Posts: 149
Joined: Sat Nov 17, 2012 3:58 am

Post by Count SymphoniC » Sat Oct 04, 2014 8:04 pm

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!

neologix
Very interested
Posts: 122
Joined: Mon May 07, 2007 5:19 pm
Location: New York, NY, USA
Contact:

Post by neologix » Sun Oct 05, 2014 1:37 am

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.

r57shell
Very interested
Posts: 478
Joined: Sun Dec 23, 2012 1:30 pm
Location: Russia
Contact:

Post by r57shell » Sun Oct 05, 2014 1:41 am

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

Count SymphoniC
Very interested
Posts: 149
Joined: Sat Nov 17, 2012 3:58 am

Post by Count SymphoniC » Sun Oct 05, 2014 1:51 am

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.
Last edited by Count SymphoniC on Sun Oct 05, 2014 3:52 am, edited 4 times in total.

neologix
Very interested
Posts: 122
Joined: Mon May 07, 2007 5:19 pm
Location: New York, NY, USA
Contact:

Post by neologix » Sun Oct 05, 2014 2:17 am

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.

Post Reply