Jump to content
Fragmare

Sound frequency and downmixing differences? Activision DPC vs. Harmony DPC+

Recommended Posts

Hey guys, I'm not a programmer, but do a lot of work with Atari chiptune & sound design, and I had a question or two about the difference in pulse frequencies between the original Activision DPC chip from Pitfall II and the newer DPC+ chip in the Harmony cartridge...

 

I've done some tests with sound output from dpctuner.bin, and as we all know, the original DPC has 256 frequencies (really only 224, as 224-255 seem to all be exactly the same)... however, I've noticed only a select few of those frequencies are "pure".  The rest seem to be interpolated, microtonal approximations of other "in-between" frequencies.  Of which, about ~50 or so line up fairly well with actual note frequencies, when making a note table.

 

My questions are...

 

Does the DPC+ offer better tuning and more pure frequencies than the regular DPC?

If so, is there a ROM file out there that showcases all the frequencies of the DPC+, like dpctuner.bin does for the normal DPC?

 

Also, how do either of these chips handle volume when downmixing their 3 channels into a single TIA channel?

Do they have their 3 channels pre-set to a volume level that will never exceed the TIA's 4-bit volume register, even when all 3 are combined?  Or do they simply mix all 3 channels, and let the TIA's 4-bit volume register clip off any excess that might exceed max volume?

 

Any insight or advice would be appreciated  :)

Share this post


Link to post
Share on other sites

After we created DPC+ I posted a demo here where you'll find the frequency table we used:

 

DPC_frequencies.h

 

It's set up for 256 values, though we only defined 89 of them (0 which is silent, and 1-88 which are A0-C8).  The values are included as part of your project, so you can change them to whatever values you wish. 

 

They are also copied into RAM for performance reasons.  If the DPC+ project is using ARM code then the table is cut in half to 128 values and the freed up 512 bytes are reallocated for the C variable and stack space.

 

Since they are in RAM they can be redefined on the fly.  For Stay Frosty 2 I needed to free up more RAM for the C variables and stack, so I reduced the table to just 6 frequency values (0-5), which are calculated on the fly as the music plays by using this data table and function

 

const unsigned int pitches[12] = {
    476196134, // C7s   77
    504512230, // D7    78
    534512088, // D7s   79
    566295831, // E7    80
    599969533, // F7    81
    635645578, // F7s   82
    673443031, // G7    83
    713488038, // G7s   84
    755914244, // A7    85
    800863244, // A7s   86
    848485051, // B7    87
    898938597  // C8    88
};

int get_pitch(int note)
{
    // on the fly frequency calculations.  Saves from having to store
    // 88 values in both ROM and RAM.

    // note will be 1-88.  1=A0, 88=c8
    int scale;
    scale = 0;

    while (note < 77)
    {
        scale++;
        note+=12;
    }
    return (pitches[note-77]) >> scale;
}

 

  • Like 1

Share this post


Link to post
Share on other sites

There's one other difference as well.  With DPC the waveforms are always square waves.  In DPC+ you get to define them, I used the following waveform definitions in the DPC+ demo. Values range from 0-5 (except for noise) as when DPC+ add the waveforms for the 3 voices together the value should be between 0-15 to sound correct.

 

SOUND_OFF = (* & $1fff)/32
	.byte 0,0,0,0,0,0,0,0
	.byte 0,0,0,0,0,0,0,0
	.byte 0,0,0,0,0,0,0,0
	.byte 0,0,0,0,0,0,0,0 

	align 32
SINE_WAVE = (* & $1fff)/32
	.byte 3,3,3,4,4,5,5,5
	.byte 5,5,5,5,4,4,3,3
	.byte 3,2,2,1,1,0,0,0
	.byte 0,0,0,0,1,1,2,2 

	align 32
TRIANGLE_WAVE = (* & $1fff)/32
	.byte 0,0,1,1,1,2,2,2
	.byte 3,3,3,4,4,4,5,5
	.byte 5,5,4,4,4,3,3,3
	.byte 2,2,2,1,1,1,0,0
	
 	align 32
SAWTOOTH_WAVE = (* & $1fff)/32
	.byte 0,0,0,0,1,1,1,1
	.byte 1,1,2,2,2,2,2,2
	.byte 3,3,3,3,3,3,4,4
	.byte 4,4,4,4,5,5,5,5
	
	align 32
SQUARE_WAVE_VOL5 = (* & $1fff)/32
	.byte 0,0,0,0,0,0,0,0
	.byte 0,0,0,0,0,0,0,0
	.byte 5,5,5,5,5,5,5,5
	.byte 5,5,5,5,5,5,5,5

	align 32
SQUARE_WAVE_VOL4 = (* & $1fff)/32
	.byte 0,0,0,0,0,0,0,0
	.byte 0,0,0,0,0,0,0,0
	.byte 4,4,4,4,4,4,4,4
	.byte 4,4,4,4,4,4,4,4

	align 32
SQUARE_WAVE_VOL3 = (* & $1fff)/32
	.byte 0,0,0,0,0,0,0,0
	.byte 0,0,0,0,0,0,0,0
	.byte 3,3,3,3,3,3,3,3
	.byte 3,3,3,3,3,3,3,3
	
	align 32
NOISE_WAVE = (* & $1fff)/32
	.byte  7, 1, 9,10, 2, 8, 8,14
	.byte  3,13, 8, 5,12, 2, 3, 7
	.byte  7, 1, 8, 4,15, 1,13, 5
	.byte  8, 5,11, 6, 8, 7, 9, 2

 

 

Share this post


Link to post
Share on other sites

Like the frequencies, the waveforms end up in RAM for performance reasons.  As such you can manipulate them on the fly.  For Stay Frosty 2 I used these definitions:

 

SOUND_OFF = (* & $1fff)/32  ; need this for no-music/2 sound effect
    .byte   0,  0,  0,  0,  0,  0,  0,  0
    .byte   0,  0,  0,  0,  0,  0,  0,  0
    .byte   0,  0,  0,  0,  0,  0,  0,  0
    .byte   0,  0,  0,  0,  0,  0,  0,  0

SQUARE_255:     ; wave 0
    .byte   0,  0,  0,  0,  0,  0,  0,  0
    .byte 255,255,255,255,255,255,255,255
    .byte 255,255,255,255,255,255,255,255
    .byte   0,  0,  0,  0,  0,  0,  0,  0

SAWTOOTH_255:   ; wave 1
    .byte   7, 15, 23, 31, 39, 47, 55, 63
    .byte  71, 79, 87, 95,103,111,119,127
    .byte 135,143,151,159,167,175,183,191
    .byte 199,207,215,223,231,239,247,255

SINE_255:       ; wave 2
    .byte   1,  3, 11, 22, 38, 57, 79,103
    .byte 128,153,177,199,218,234,245,253
    .byte 255,253,245,234,218,199,177,153
    .byte 128,103, 79, 57, 38, 22, 11,  3

SINE_SINE_255:  ; wave 3
    .byte   0,  8, 30, 64,105,148,188,221
    .byte 243,253,250,238,219,198,180,167
    .byte 162,167,180,198,219,238,250,253
    .byte 243,221,188,148,105, 64, 30,  8

CLARINET_WAVE:  ; wave 4
    .byte 111,171,221,251,255,237,205,173
    .byte 151,146,158,180,202,213,205,177
    .byte 135, 88, 50, 28, 26, 41, 65, 86
    .byte  95, 86, 63, 32,  8,  0, 15, 55

OBOE_WAVE:      ; wave 5
    .byte 172,202,233,255,255,230,189,148
    .byte 121,110,107,106,107,116,138,167
    .byte 187,186,161,123, 90, 70, 62, 52
    .byte  35, 13,  0,  7, 35, 74,113,144

BANJO_WAVE:     ; wave 6
    .byte 127,207,254,255,223,183,156,143
    .byte 132,117, 99, 90, 98,118,135,138
    .byte 127,117,119,137,157,164,156,138
    .byte 122,112, 99, 71, 32,  0,  0, 47

 

you'll note those are using 0-255.  Stay Frosty 2 was written to merge waveforms together on the fly to create additional instrument sounds. The routine that does it will reduce the merged value to 0-VOLUME where VOLUME is the current voice volume of 0-5:

        for(j=0;j<32;j++)
            WAVEFORM_BUFFER[dest+j] = (volume * (WAVEFORM_DEFINITIONS[wave+j] + WAVEFORM_DEFINITIONS[wave2+j])) >> 9;

 

Share this post


Link to post
Share on other sites
On 3/8/2021 at 1:37 PM, SpiceWare said:

After we created DPC+ I posted a demo here where you'll find the frequency table we used:

 

DPC_frequencies.h 4.84 kB · 4 downloads

 

It's set up for 256 values, though we only defined 89 of them (0 which is silent, and 1-88 which are A0-C8).  The values are included as part of your project, so you can change them to whatever values you wish. 

 

They are also copied into RAM for performance reasons.  If the DPC+ project is using ARM code then the table is cut in half to 128 values and the freed up 512 bytes are reallocated for the C variable and stack space.

 

Since they are in RAM they can be redefined on the fly.  For Stay Frosty 2 I needed to free up more RAM for the C variables and stack, so I reduced the table to just 6 frequency values (0-5), which are calculated on the fly as the music plays by using this data table and function

 

Interesting!  This actually does help a bit!

 

So each of the three DPC+ channels can have volume ranging from 0-5?  Not just 0 *or* 5?  If so, this, in and of itself, is a HUGE advantage over the regular DPC which has no volume control whatsoever.  This means you can potentially create enveloped instruments that sound MUCH better than they would on regular DPC.

The ability to create rudimentary wavetables is also great!  Especially for Namco arcade conversions where the original arcade sound hardware was wavetable based in the first place.

 

I did want to ask for clarification about something regarding the DPC+ frequencies, though...

 

Most old PSG chips have their available frequencies defined by the hardware itself, using pitch dividers.  Usually, these will be arranged in a sort of gradient, where the frequencies are bunched up and closer together on the lower, bassy end and spaced further apart the higher up the frequency scale you go.  You then must define a note table that uses only the hardware defined frequencies available.  This can sometimes lead to tuning issues, depending on the number of hardware defined frequencies that are available (such as with the TIA, or to lesser extent, the original DPC)

 

You're saying that the DPC+ note table is user defined, and can contain up to 256 entries (128 if ARM is in use).  My question is this... must these 128 or 256 user defined note table entries also adhere to some sort of master frequency table defined by the DPC+ hardware itself, like older PSG chips?  Or is it one of those things where the hardware defined frequencies of the DPC+ are so ridiculously numerous that you can essentially get perfect or near perfect tuning, if you want?

Share this post


Link to post
Share on other sites
3 hours ago, Fragmare said:

So each of the three DPC+ channels can have volume ranging from 0-5?  Not just 0 *or* 5?

 

correct - you could even do something like:

  • channel 0 volume 0-7
  • channel 1 volume 0-4
  • channel 2 volume 0-4

or

  • channel 0 volume 0-6
  • channel 1 volume 0-5
  • channel 2 volume 0-4

all that matters is the sum of the max volumes does not exceed 15.

 

By changing the volume on the fly you can implement ADSR.  I did that in Stay Frosty 2 using these ADSR definitions:

 

ADSR_Data:
    .byte 2,3,4,5,6,6,6,6   ; ADSR 0
    .byte 5,5,5,5,4,4,4,4
    .byte 3,3,3,3,2,2,2,2
    .byte 2,2,2,2,2,2,2,2

    .byte 2,2,3,3,4,4,5,5   ; ADSR 1
    .byte 6,6,6,6,6,6,6,6
    .byte 6,6,6,6,6,6,6,6
    .byte 6,6,6,6,6,6,6,6

    .byte 2,3,4,5,6,6,6,6   ; ADSR 2
    .byte 5,5,5,5,5,5,5,5
    .byte 4,4,4,4,4,4,4,4
    .byte 3,3,3,3,3,3,3,3

    .byte 6,5,4,4,3,3,3,2   ; ADSR 3 supercat
    .byte 2,2,2,0,0,0,0,0
    .byte 0,0,0,0,0,0,0,0
    .byte 0,0,0,0,0,0,0,0

    .byte 2,4,6,6,6,6,6,6  ; ADSR 4 iesposta
    .byte 6,6,6,6,6,6,6,6
    .byte 6,6,6,6,6,6,6,6
    .byte 6,6,6,6,6,6,6,6

 

The ADSR values range from 0-6 because the calculations are done using byte values, which range in value from 0-255.  (255 * 6 )/ 256 = 5.9, which gets truncated to 5 because it's done using integer math.

 

You can hear Stay Frosty 2's music here:

 

or play it yourself using the ROM which is available here.

 

The values in the frequency table are used in the DPC+ driver that runs on the ARM. I'm not familiar with the specifics, but am with the Stella implication:

inline void CartridgeDPCPlus::updateMusicModeDataFetchers()
{
  // Calculate the number of cycles since the last update
  uInt32 cycles = uInt32(mySystem->cycles() - myAudioCycles);
  myAudioCycles = mySystem->cycles();

  // Calculate the number of DPC+ OSC clocks since the last update
  double clocks = ((20000.0 * cycles) / 1193191.66666667) + myFractionalClocks;
  uInt32 wholeClocks = uInt32(clocks);
  myFractionalClocks = clocks - double(wholeClocks);

  // Let's update counters and flags of the music mode data fetchers
  if(wholeClocks > 0)
    for(int x = 0; x <= 2; ++x)
      myMusicCounters[x] += myMusicFrequencies[x] * wholeClocks;
}


...
          case 0x05: // AMPLITUDE
          {
            // Update the music data fetchers (counter & flag)
            updateMusicModeDataFetchers();

            // using myDisplayImage[] instead of myProgramImage[] because waveforms
            // can be modified during runtime.
            uInt32 i = myDisplayImage[(myMusicWaveforms[0] << 5) + (myMusicCounters[0] >> 27)] +
                       myDisplayImage[(myMusicWaveforms[1] << 5) + (myMusicCounters[1] >> 27)] +
                       myDisplayImage[(myMusicWaveforms[2] << 5) + (myMusicCounters[2] >> 27)];

            result = uInt8(i);
            break;
          }

 

myMusicCounter[x] is an array of 3 values, one for each voice. It's a 32 bit value to which myMusicFrequency[x] is added, which is one of the values in the 256 (or 128) entry frequency table.

 

myMusicCounter is a 32 bit value - the topmost 5 bits define a value of 0-31, which is the current position in the waveform for that voice.  After it hits 31 it will wrap back to 0, pointing at the start of the waveform.

Share this post


Link to post
Share on other sites
19 hours ago, SpiceWare said:

all that matters is the sum of the max volumes does not exceed 15.

I am no musician, but we know that the TIA volumes are not linear (e.g. 15 is only twice as loud as 6). Which that means that the volumes of the different channels affect each other. 

 

Example (2 channels)

  • Ch0: volume  0, Ch1: volume 5 -> Ch1 increases the total volume by 42.9%
  • Ch0: volume  5, Ch1: volume 5 -> Ch1 increases the total volume by 32.1% (playing both at 6 would compensate the compression)
  • Ch0: volume 10, Ch1: volume 5 -> Ch1 increases the total volume by 25.0% (not possible to compensate)

Has anyone ever tried to compensate the volume compression? 

Share this post


Link to post
Share on other sites
2 hours ago, Thomas Jentzsch said:

Has anyone ever tried to compensate the volume compression?

Not that I'm aware of

Share this post


Link to post
Share on other sites
On 3/8/2021 at 8:48 PM, SpiceWare said:

Like the frequencies, the waveforms end up in RAM for performance reasons.  As such you can manipulate them on the fly.  For Stay Frosty 2 I used these definitions:

 

SOUND_OFF = (* & $1fff)/32  ; need this for no-music/2 sound effect
    .byte   0,  0,  0,  0,  0,  0,  0,  0
    .byte   0,  0,  0,  0,  0,  0,  0,  0
    .byte   0,  0,  0,  0,  0,  0,  0,  0
    .byte   0,  0,  0,  0,  0,  0,  0,  0

SQUARE_255:     ; wave 0
    .byte   0,  0,  0,  0,  0,  0,  0,  0
    .byte 255,255,255,255,255,255,255,255
    .byte 255,255,255,255,255,255,255,255
    .byte   0,  0,  0,  0,  0,  0,  0,  0

SAWTOOTH_255:   ; wave 1
    .byte   7, 15, 23, 31, 39, 47, 55, 63
    .byte  71, 79, 87, 95,103,111,119,127
    .byte 135,143,151,159,167,175,183,191
    .byte 199,207,215,223,231,239,247,255

SINE_255:       ; wave 2
    .byte   1,  3, 11, 22, 38, 57, 79,103
    .byte 128,153,177,199,218,234,245,253
    .byte 255,253,245,234,218,199,177,153
    .byte 128,103, 79, 57, 38, 22, 11,  3

SINE_SINE_255:  ; wave 3
    .byte   0,  8, 30, 64,105,148,188,221
    .byte 243,253,250,238,219,198,180,167
    .byte 162,167,180,198,219,238,250,253
    .byte 243,221,188,148,105, 64, 30,  8

CLARINET_WAVE:  ; wave 4
    .byte 111,171,221,251,255,237,205,173
    .byte 151,146,158,180,202,213,205,177
    .byte 135, 88, 50, 28, 26, 41, 65, 86
    .byte  95, 86, 63, 32,  8,  0, 15, 55

OBOE_WAVE:      ; wave 5
    .byte 172,202,233,255,255,230,189,148
    .byte 121,110,107,106,107,116,138,167
    .byte 187,186,161,123, 90, 70, 62, 52
    .byte  35, 13,  0,  7, 35, 74,113,144

BANJO_WAVE:     ; wave 6
    .byte 127,207,254,255,223,183,156,143
    .byte 132,117, 99, 90, 98,118,135,138
    .byte 127,117,119,137,157,164,156,138
    .byte 122,112, 99, 71, 32,  0,  0, 47

 

you'll note those are using 0-255.  Stay Frosty 2 was written to merge waveforms together on the fly to create additional instrument sounds. The routine that does it will reduce the merged value to 0-VOLUME where VOLUME is the current voice volume of 0-5:

        for(j=0;j<32;j++)
            WAVEFORM_BUFFER[dest+j] = (volume * (WAVEFORM_DEFINITIONS[wave+j] + WAVEFORM_DEFINITIONS[wave2+j])) >> 9;

 

do these waves use the pcm2pwm program?

Share this post


Link to post
Share on other sites
22 minutes ago, atari2601 said:

do these waves use the pcm2pwm program?

 

Never heard of pcm2pwm.  I found this this:

 

Quote

It's output is intended for use with any 1-bit audio devices which require a simple playback routine and/or relatively high degree of compression.

 

which says it's for 1-bit audio, DPC+ generates 4-bit digital audio.

Share this post


Link to post
Share on other sites
1 hour ago, SpiceWare said:

 

Never heard of pcm2pwm.  I found this this:

 

 

which says it's for 1-bit audio, DPC+ generates 4-bit digital audio.

Thats the program but what did you use to make the samples

Share this post


Link to post
Share on other sites
1 hour ago, atari2601 said:

Thats the program but what did you use to make the samples

 

Numerous people helped with the music in Stay Frosty 2.  I don't recall specifics, this was back in 2012 or so, but some of the 32 byte waveforms were most likely created via the attached spreadsheets that I found in the source for SF2. You can open them using OpenOffice or LibreOffice. I used to use OpenOffice but switched to LibreOffice, I believe that was due to @Thomas Jentzsch's recommendation.

 

waveforms.zip

 

Share this post


Link to post
Share on other sites
On 3/13/2021 at 10:12 PM, Thomas Jentzsch said:

I am no musician, but we know that the TIA volumes are not linear (e.g. 15 is only twice as loud as 6).

From work I have done on other embedded devices (not Atari), you can double the amplitude of the output waveform and it will not sound like double the volume to our ears.
That's because the device is using linear values but our ears work on logarithm values.
Most TV's with digital volume also suffer from this (ie volume 10% sounds okay but to double the perceived volume you need to go way up to 30% or more)

 

For my device (which only needs simple buzzer-like sounds at various, fixed, frequencies), I set the amplitude according to the crude formula:

amplitude = (vol^2)/100

where:

volume is the desired volume 0..100 %

amplitude is the amplitude of the output waveform 0..100 %

Not truly logarithmic but parabolic is close enough.

With this, the user can double the desired volume value and the perceived sound also appears to double.

  • Like 1

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...