Hi,
So, I've played around some more.....
It won't run at anything below 16MHz. Even at 16MHz the interrupt routine, executing at 10kHz cosumes 70% of the available CPU cycles.
I've only tried it on an 18F25K20 @64MHz, you try it on a 16F or 12F1840 and see if it works.

You can not cut'n'paste this into a program using DT-Ints. Try it stand-alone first and we'll see about DT-ints later - if needed.

/Henrik.

Code:
'****************************************************************
'*  Name    : 3xSound generator.pbp                             *
'*  Author  : Henrik Olsson                                     *
'*  Notice  : Written by Henrik Olsson 2015                     *
'*          : Free software, use as you wish.                   *
'*  Date    : 2015-01-14                                        *
'*  Version : 1.0                                               *
'*  Notes   : Written in response to a thread on the PBP forum: *
'*          : www.picbasic.co.uk/forum/showthread.php?t=19684   *
'*                                                              *
'*          : Tones are generated using the DDS tecnique with a *
'*            simple 16bit accumulator to which a value gets    *
'*            added once every interrupt (100us). When the      *
'*            accumulator overflows the output is toggled.      *
'*            Three generators are incorporated since that was  *
'*            the request. More could be added if the PIC is    *
'*            clocked fast enough. With three generators 16MHz  *
'*            is the lowest you want to use. The interrupts     *
'*            then uses ~70% of the available CPU cycles when   *
'*            three tones are generated.                        *
'*                                                              *
'*            Version history:                                  *
'*              v1.0 2015-01-14 - Initial version               *
'*                                                              *
'****************************************************************

' CONFIG's, TRIS's, ANSEL's CMCON's and anything device specific is not shown

DEFINE OSC 64
DEFINE INTHAND Generate         ' We're treating the interrupt as an ASM routine (see notes!)

'-------------------------------------------------------------------------------------------
' These variables and alises are needed by the sound generator and needs to be
' accessed by the user.
Channel_1       VAR PortC.0     ' Output for sound channel 1
Channel_2       VAR PortC.1     ' Output for sound channel 2
Channel_3       VAR PortC.2     ' Output for sound channel 3

TMR2IF          VAR PIR1.1      ' Alias to TMR2 Interrupt Request Flag - verify against datasheet.

Output_1        VAR WORD        ' Desired output frequency, Channel 1, 13.107 units per Hz
Output_2        VAR WORD        ' Desired output frequency, Channel 2, 13.107 units per Hz
Output_3        VAR WORD        ' Desired output frequency, Channel 3, 13.107 units per Hz

Duration_1      VAR WORD        ' Duration of tone, Channel 1, 100us units.
Duration_2      VAR WORD        ' Duration of tone, Channel 2, 100us units.
Duration_3      VAR WORD        ' Duration of tone, Channel 3, 100us units.

'-------------------------------------------------------------------------------------------
' These are variables used by the interrupt service routine.
' No user servicable parts inside......
R0_SHADOW       VAR WORD        ' Variable used for saving PBP system variables in the ISR
R1_SHADOW       VAR WORD        ' Variable used for saving PBP system variables in the ISR
R4_SHADOW       VAR WORD        ' Variable used for saving PBP system variables in the ISR

Accumulator_1   VAR WORD        ' DDS accumulator for sound generator 1
Accumulator_2   VAR WORD        ' DDS accumulator for sound generator 2
Accumulator_3   VAR WORD        ' DDS accumulator for sound generator 3

old_1           VAR WORD        ' Used to detect overflow of accumulator 1
old_2           VAR WORD        ' Used to detect overflow of accumulator 2
old_3           VAR WORD        ' Used to detect overflow of accumulator 3
'--------------------------------------------------------------------------------------------

' We want 10kHz interrupt rate, that's 100us between interrupts.
' The interrupt routine needs around ~280 instruction cycles when
' outputting on three channels.
'
' At 16MHz that would be  400 instructions - 70% used by the generator. 
' At 20MHz that would be  500 instructions - 56% used by the generator
' At 40MHz that would be 1000 instructions - 28% used by the generator
' At 64MHz that would be 1600 instructions - 17.5% used by the generator
'
' T2CON and PR2 needs to be setup so that an interrupt is triggered every 100us
' Use one of the follwing:
' PR2 =  99 : T2CON = %00000110   ' For 64MHz - 1:16 prescaler
' PR2 =  49 : T2CON = %00000110   ' For 32MHz - 1:16 prescaler
' PR2 = 124 : T2CON = %00000101   ' For 20MHz - 1:4 prescaler
' PR2 =  99 : T2CON = %00000101   ' For 16MHz - 1:4 prescaler
 
PR2 = 99 : T2CON = %00000110  'TMR2 on, postscaler 1:1, prescaler 1:4

IPR1.1   = 1        ' TMR2 high priority
PIE1.1   = 1        ' TMR2 IE
INTCON.6 = 1        ' PIE
INTCON.7 = 1        ' GIE


Main:
    ' All three notes will play simultanously for the specified duration.
    ' The sound is generated in the background so the PAUSE 1000 will
    ' execute at the same time as the notes are played. Remember though that
    ' the interrupt "steals" time so the PAUSE isn't correct. The slower the
    ' PIC clock is the more inacurate it gets.

    Output_1 = 857   : Duration_1 = 500    'Play C2 (65.4Hz) for 50ms
    Output_2 = 5767  : Duration_2 = 2500   'Play A4 (440.0Hz)for 250ms 
    Output_3 = 25893 : Duration_3 = 1000   'Play B6 (1975.5Hz) for 100ms

    Pause 1000

Goto Main


'-------------------------------------------------------------------------------
' Actual interrupt routine here. Please note that this is written in PBP but
' the interrupt is handled as if it were a ASM interrupt. As far as I can see
' it uses PBP system variables R0, R1, R4 so I'm saving/restoring those.
' If ANY code gets added here then the ASM-listning needs to be checked to 
' see of the added PBP code uses any more system variables and if so
' those needs to saved/restored as well.
' When all three channels produces output it needs ~280 instructions cycles.
@ Generate:

@ MOVE?WW R0, _R0_SHADOW                  ; Save PBP system variable R0 
@ MOVE?WW R1, _R1_SHADOW                  ; Save PBP system variable R1 
@ MOVE?WW R4, _R4_SHADOW                  ; Save PBP system variable R4   
    TMR2IF = 0

    IF Duration_1 > 0 THEN
        Accumulator_1 = Accumulator_1 + Output_1
        If Accumulator_1 < old_1 THEN Toggle Channel_1
        old_1 = Accumulator_1
        Duration_1 = Duration_1 - 1
    ELSE
        Channel_1 = 0
    ENDIF
    
    IF Duration_2 > 0 THEN
        Accumulator_2 = Accumulator_2 + Output_2
        If Accumulator_2 < old_2 THEN Toggle Channel_2
        old_2 = Accumulator_2
        Duration_2 = Duration_2 - 1
    ELSE
        Channel_2 = 0
    ENDIF
    
    If Duration_3 > 0 THEN
        Accumulator_3 = Accumulator_3 + Output_3
        If Accumulator_3 < old_3 THEN Toggle Channel_3
        old_3 = Accumulator_3
        Duration_3 = Duration_3 - 1
    ELSE
        Channel_3 = 0
    ENDIF


@ MOVE?WW _R0_SHADOW, R0                    ; Restore PBP system variable R0
@ MOVE?WW _R1_SHADOW, R1                    ; Restore PBP system variable R1 
@ MOVE?WW _R4_SHADOW, R4                    ; Restore PBP system variable R4    
 
@ retfie FAST
'-------------------------------------------------------------------------------
END