[comp.sys.next] sine wave synthesis on Next

manzara@cpsc.ucalgary.ca (Leonard Manzara) (11/25/89)

There have been a number of requests for my source code which
allows you to synthesize a sine wave on the NeXT's DAC.
Enclosed is a copy of the source.  Make sure you separate the code into three
different files:  sine.c, sine.asm, and ioequ.asm.
Compile sine.c with:  cc -ansi -lm -lsys_s -O.
Assemble sine.asm with:  asm56000 -a -b -l;  ioequ.asm is included
by sine.asm.

Leonard Manzara (manzara@cpsc.UCalgary.CA)




/*********************************************************************************
*
*  PROGRAM:     sine.c
*
*  AUTHOR:      Leonard Manzara
*
*  THIS PROGRAM GENERATES A SINE WAVE ON THE DSP AND PLAYS IT THROUGH
*  THE NeXT DAC.  BASED HEAVILY ON dsp_example_3.
*  COMPILE THIS WITH:   cc sine.c -ansi -lm -lsys_s -O
*  THE DSP ASSEMBLY PROGRAM IS:  sine.asm   
*
*********************************************************************************/

#import <sound/sound.h>
#import <sound/sounddriver.h>
#import <mach.h>
#import <stdlib.h>
#import <stdio.h>

static int read_count;

main()
{
int s_err;
kern_return_t k_err;
port_t dev_port, owner_port, cmd_port, temp_port, stream_port, read_port, reply_port;
SNDSoundStruct *dspStruct;
int reply, read_buf_size, read_width, low_water, high_water, protocol;

/*  INITIALIZE DMA STREAMING VARIABLES  */
low_water = 48*1024;
high_water = 64*1024;
read_width = 2;
read_buf_size = 512;

/*  GET THE DEVICE PORT FOR THE SOUND/DSP DRIVER ON THE LOCAL MACHINE  */
k_err = netname_look_up(name_server_port,"","sound",&dev_port);
if (k_err != KERN_SUCCESS) {
  mach_error("netname lookup failure ",k_err);
  exit(1);
}

/*  GET THE OWNER PORT  */
k_err = port_allocate(task_self(),&owner_port);
if (k_err != KERN_SUCCESS) {
  mach_error("Cannot allocate owner port ",k_err);
  exit(1);
}

/*  BECOME OWNER OF DSP RESOURCES  */
temp_port = owner_port;
k_err = snddriver_set_dsp_owner_port(dev_port,owner_port,&temp_port);
if (k_err != KERN_SUCCESS) {
  mach_error("Cannot become owner of dsp resources ",k_err);
  exit(1);
}

/*  GET THE DSP COMMAND PORT  */
k_err = snddriver_get_dsp_cmd_port(dev_port,owner_port,&cmd_port);
if (k_err != KERN_SUCCESS) {
  mach_error("Cannot acquire command port ",k_err);
  exit(1);
}

/*  ACQUIRE OWNERSHIP OF THE SOUND OUTPUT DEVICE  */
k_err = snddriver_set_sndout_owner_port(dev_port,owner_port,&temp_port);
if (k_err != KERN_SUCCESS) {
  mach_error("Cannot become owner of sound out device ",k_err);
  exit(1);
}

/*  SET UP TO STREAM DATA FROM THE DSP TO THE SOUND DEVICE  */
protocol = 0;
k_err = snddriver_stream_setup(dev_port,owner_port,SNDDRIVER_STREAM_DSP_TO_SNDOUT_44,
			       read_buf_size,read_width,low_water,high_water,
			       &protocol,&read_port);
if (k_err != KERN_SUCCESS) {
  mach_error("Cannot set up stream from DSP ",k_err);
  exit(1);
}

/*  SET THE DSP PROTOCOL  */
k_err = snddriver_dsp_protocol(dev_port,owner_port,protocol);
if (k_err != KERN_SUCCESS) {
  mach_error("Cannot set up DSP protocol ",k_err);
  exit(1);
}

/*  ALLOCATE A PORT FOR REPLIES  */
k_err = port_allocate(task_self(),&reply_port);
if (k_err != KERN_SUCCESS) {
  mach_error("Cannot allocate reply port ",k_err);
  exit(1);
}

/*  PARSE THE .LOD ASSEMBLY FILE AND BOOT THE DSP WITH IT  */
s_err = SNDReadDSPfile("sine.lod",&dspStruct,NULL);
if (s_err != SND_ERR_NONE) {
  fprintf(stderr,"Cannot parse DSP load image: %s\n",SNDSoundError(s_err));
  exit(1);
}

s_err = SNDBootDSP(dev_port,owner_port,dspStruct);
if (s_err != SND_ERR_NONE) {
  fprintf(stderr,"Cannot boot dsp: %s\n",SNDSoundError(s_err));
  exit(1);
}

/*  START STREAM FROM THE DSP  */
k_err = snddriver_stream_start_reading(read_port,NULL,
				       read_count,0,
				       0,0,0,0,0,0,reply_port);
if (k_err != KERN_SUCCESS) {
  mach_error("Cannot enqueue read request ",k_err);
  exit(1);
}

/*  SEND INTIAL TABLE VALUE TO DSP  */
k_err = snddriver_dspcmd_req_condition(cmd_port,SNDDRIVER_ISR_HF3,SNDDRIVER_ISR_HF3,
				       SNDDRIVER_HIGH_PRIORITY,PORT_NULL);
if (k_err != KERN_SUCCESS) {
  mach_error("Cannot request condition ",k_err);
  exit(1);
}
printf("Enter table value: ");
scanf("%d",&reply);
getchar();
k_err = snddriver_dsp_write(cmd_port,&reply,1,4,SNDDRIVER_MED_PRIORITY);
if (k_err != KERN_SUCCESS) {
  mach_error("Cannot write to DSP ",k_err);
  exit(1);
}
k_err = snddriver_dspcmd_req_condition(cmd_port,SNDDRIVER_ISR_HF3,0,
				       SNDDRIVER_HIGH_PRIORITY,PORT_NULL);
if (k_err != KERN_SUCCESS) {
  mach_error("Cannot request condition ",k_err);
  exit(1);
}


/*  SEND HOST COMMAND TO THE DSP CHIP;  VECTOR = P:$0026
    CAUSES PROGRAM CONTROL TO JMP TO MAIN, WHERE SOUND SAMPLES ARE SENT OUT.
    NOTE THAT THE HOST COMMAND IS THE VECTOR ADDRESS DIVIDED BY 2  */
printf("Push return to start sine wave:");
reply = getchar();
k_err = snddriver_dsp_host_cmd(cmd_port,(u_int)0x13,SNDDRIVER_HIGH_PRIORITY);
if (k_err != KERN_SUCCESS) {
  mach_error("Cannot send host command ",k_err);
  exit(1);
}

/*  PLAY THE SINE TONE UNTIL RETURN PUSHED AGAIN.  VECTOR = p:$0028
    CAUSES PROGRAM CONTROL TO JMP TO RESET, EFFECTIVELY STOPPING SAMPLE OUT  */
printf("Push return to stop sine wave:");
reply = getchar();
k_err = snddriver_dsp_host_cmd(cmd_port,(u_int)0x14,SNDDRIVER_HIGH_PRIORITY);
if (k_err != KERN_SUCCESS) {
  mach_error("Cannot send host command ",k_err);
  exit(1);
}
}




;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;   PROGRAM:	sine.asm
;;
;;   AUTHOR:	Leonard Manzara
;;
;;   THIS PROGRAM, WHEN USED IN CONJUNCTION WITH sine.c, PRODUCES A SINE TONE THROUGH
;;   THE NeXT'S D/A CONVERTER.  ASSEMBLE THIS WITH:  asm56000 -a -b -l sine.asm
;;   BASED HEAVILY ON  dsp_example_3.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	include	'ioequ.asm'


;;  MEMORY STORAGE
DMA_DONE	equ	0
x_DMA_flags	equ	$00ff
x_OutputValue	equ	$00fe

y_DMA_size	equ	$00ff
y_Buffer_count	equ	$00fe

XRAMLO		equ	8192
YTABLERAM	equ	8192

TABLE_LENGTH	equ	1

DMA_R_REQ	equ	$050001		; message to host to request DMA transfer


;; INTERRUPT VECTORS
	org	p:$0
	jmp	<reset			; reset interrupt

	org	p:$0024			; set flag when dma transfer done
	bset	#DMA_DONE,x:x_DMA_flags

	org	p:$0026			; host command to start sine wave send
	jmp	<main

	org	p:$0028			; host command to stop sine wave send
	jmp	<reset



;; RESET SUBROUTINE
	org	p:$40
reset
	movec	#6,omr			; chip set to mode 2; ROM enabled
	bset	#0,x:m_pbc		; configure port B so it acts as host interface
	bset	#3,x:m_pcddr		; program pin 3 (pc3) of port C to be output
	
	bclr	#3,x:m_pcd		; zero to enable the external ram
	
	movep	#>$000000,x:m_bcr	; set 0 wait states for all external RAM
	movep	#>$00b400,x:m_ipr	; set interrupt priority register to SSI=0, SCI=1, HOST=0

	move	#>$0200,a
	move	a,y:y_DMA_size		; set buffersize to 512
	move	#>XRAMLO,r7		; set pointer to beginning of DMA buffer

	clr	a
	move	a,x:x_DMA_flags		; clear DMA flags
	move 	a,y:y_Buffer_count	; set Buffer count to 0

	jsr	read_table		; read initial table value(s)
	move	y:YTABLERAM,n2		; intialize n2, for pitch
	nop				; waste a cycle to let n2 set

	bset	#m_hcie,x:m_hcr		; enable host command interrupts
	move	#0,sr			; unmask interrupts

_loop	jmp	_loop			; loop until interrupt sends us to main



;; MAIN ROUTINE
main
	move	#>$0100,r2		; load in origin of sine table into r2
	move	#>$0100-1,m2		; make it mod 256
	nop				; waste one cycle to allow r2 and m2 to be initialized

_toploop
	move	y:(r2)+n2,a		; read value from sine table
	rep	#8
	  asr	a			; shift the value right to avoid clipping
	jsr	putHost			; since stereo put it into
	jsr	putHost			; DMA buffer twice
	jmp	_toploop		; loop forever



;; PUTHOST SUBROUTINE
putHost
	move	a,x:x_OutputValue	; store a, so we can restore it at end of routine
	move	a,x:(r7)+		; put output value into DMA register, increment pointer

	move	y:y_Buffer_count,b
	move	#>1,a
	add	a,b
	move	b,y:y_Buffer_count	; increment buffer count by 1

	move	y:y_DMA_size,a
	cmp	b,a
	jgt	_exit			; if Buffer count < DMA size, exit
					; (only send DMA buffer when it is full)

_transfer
	jclr	#m_htde,x:m_hsr,_transfer	; loop until htde bit HSR is set
	movep	#DMA_R_REQ,x:m_htx	; send DMA initiate read message to host

_ackBegin
	jclr	#m_hf1,x:m_hsr,_ackBegin	; loop until host acknowledges (HF1=1)

	move	#>XRAMLO,r7		; point to beginning of DMA buffer
	move	y:y_DMA_size,b		; initialize loop size
	do	b,_send_loop		; top of DMA buffer send loop
_send	jclr	#m_htde,x:m_hsr,_send	; loop until htde bit of HSR is set
	movep	x:(r7)+,x:m_htx		; send buffer element to host
_send_loop
	btst	#DMA_DONE,x:x_DMA_flags	; if interrupt has set flags, then go to end
	jcs	_endDMA
	jclr	#m_htde,x:m_hsr,_send_loop	; else keep sending zeroes until interrupt sets flags
	movep	#0,x:m_htx
	jmp	_send_loop
_endDMA
	bclr	#DMA_DONE,x:x_DMA_flags	; reset flag to 0, we know we are done--be ready to
					; set flag on next interrupt
_ackEnd
	jset	#m_hf1,x:m_hsr,_ackEnd	; loop until host acknowledge has ended (HF1=0)
	move	#>XRAMLO,r7		; reset DMA buffer pointer to beginning
	clr	a			; reset buffer count
	move	a,y:y_Buffer_count
_exit	move	x:x_OutputValue,a	; put output value into a, in case it's needed by the
	rts				; calling routine



read_table
	bset	#m_hf3,x:m_hcr		; set HF3 (tells host that DSP ready to receive data)
	move	#>YTABLERAM,r3		; reset pointer to beginning of table

	do	#TABLE_LENGTH,_tableloop	; loop until table filled
_loop	jclr	#m_hrdf,x:m_hsr,_loop	; loop until hrdf bit of HSR becomes set
	movep	x:m_hrx,y:(r3)+		; move data into y memory; increment pointer
_tableloop

	bclr	#m_hf3,x:m_hcr		; clear HF3 (tells host that DSP finished reading data)
	rts;



;************************************************************************
;
;	ioequ.asm
;
; This program originally available on the Motorola DSP bulletin board.
; It is provided under a DISCLAMER OF WARRANTY available from
; Motorola DSP Operation, 6501 Wm. Cannon Drive W., Austin, Tx., 78735.
; 
; Motorola Standard I/O Equates (lower case).
; 
; Last Update 25 Aug 87   Version 1.1  (fixed m_of)
;
;************************************************************************
;
;       EQUATES for DSP56000 I/O registers and ports
;
;************************************************************************

ioequlc ident   1,0

;------------------------------------------------------------------------
;
;       EQUATES for I/O Port Programming
;
;------------------------------------------------------------------------

;       Register Addresses

m_bcr   EQU     $FFFE           ; Port A Bus Control Register
m_pbc   EQU     $FFE0           ; Port B Control Register
m_pbddr EQU     $FFE2           ; Port B Data Direction Register
m_pbd   EQU     $FFE4           ; Port B Data Register
m_pcc   EQU     $FFE1           ; Port C Control Register
m_pcddr EQU     $FFE3           ; Port C Data Direction Register
m_pcd   EQU     $FFE5           ; Port C Data Register


;------------------------------------------------------------------------
;
;       EQUATES for Host Interface
;
;------------------------------------------------------------------------

;       Register Addresses

m_hcr   EQU     $FFE8           ; Host Control Register
m_hsr   EQU     $FFE9           ; Host Status Register
m_hrx   EQU     $FFEB           ; Host Receive Data Register
m_htx   EQU     $FFEB           ; Host Transmit Data Register

;       Host Control Register Bit Flags

m_hrie  EQU     0               ; Host Receive Interrupt Enable
m_htie  EQU     1               ; Host Transmit Interrupt Enable
m_hcie  EQU     2               ; Host Command Interrupt Enable
m_hf2   EQU     3               ; Host Flag 2
m_hf3   EQU     4               ; Host Flag 3

;       Host Status Register Bit Flags

m_hrdf  EQU     0               ; Host Receive Data Full
m_htde  EQU     1               ; Host Transmit Data Empty
m_hcp   EQU     2               ; Host Command Pending
m_hf    EQU     $18             ; Host Flag Mask
m_hf0   EQU     3               ; Host Flag 0
m_hf1   EQU     4               ; Host Flag 1
m_dma   EQU     7               ; DMA Status

;------------------------------------------------------------------------
;
;       EQUATES for Serial Communications Interface (SCI)
;
;------------------------------------------------------------------------

;       Register Addresses

m_srxl  EQU     $FFF4           ; SCI Receive Data Register (low)
m_srxm  EQU     $FFF5           ; SCI Receive Data Register (middle)
m_srxh  EQU     $FFF6           ; SCI Receive Data Register (high)
m_stxl  EQU     $FFF4           ; SCI Transmit Data Register (low)
m_stxm  EQU     $FFF5           ; SCI Transmit Data Register (middle)
m_stxh  EQU     $FFF6           ; SCI Transmit Data Register (high)
m_stxa  EQU     $FFF3           ; SCI Transmit Data Address Register
m_scr   EQU     $FFF0           ; SCI Control Register
m_ssr   EQU     $FFF1           ; SCI Status Register
m_sccr  EQU     $FFF2           ; SCI Clock Control Register

;       SCI Control Register Bit Flags

m_wds   EQU     $3              ; Word Select Mask
m_wds0  EQU     0               ; Word Select 0
m_wds1  EQU     1               ; Word Select 1
m_wds2  EQU     2               ; Word Select 2
m_sbk   EQU     4               ; Send Break
m_wake  EQU     5               ; Wake-up Mode Select
m_rwi   EQU     6               ; Receiver Wake-up Enable
m_woms  EQU     7               ; Wired-OR Mode Select
m_re    EQU     8               ; Receiver Enable
m_te    EQU     9               ; Transmitter Enable
m_ilie  EQU     10              ; Idle Line Interrupt Enable
m_rie   EQU     11              ; Receive Interrupt Enable
m_tie   EQU     12              ; Transmit Interrupt Enable
m_tmie  EQU     13              ; Timer Interrupt Enable

;       SCI Status Register Bit Flags

m_trne  EQU     0               ; Transmitter Empty
m_tdre  EQU     1               ; Transmit Data Register Empty
m_rdrf  EQU     2               ; Receive Data Register Full
m_idle  EQU     3               ; Idle Line
m_or    EQU     4               ; Overrun Error
m_pe    EQU     5               ; Parity Error
m_fe    EQU     6               ; Framing Error
m_r8    EQU     7               ; Received Bit 8

;       SCI Clock Control Register Bit Flags

m_cd    EQU     $FFF            ; Clock Divider Mask
m_cod   EQU     12              ; Clock Out Divider
m_scp   EQU     13              ; Clock Prescaler
m_rcm   EQU     14              ; Receive Clock Source
m_tcm   EQU     15              ; Transmit Clock Source

;------------------------------------------------------------------------
;
;       EQUATES for Synchronous Serial Interface (SSI)
;
;------------------------------------------------------------------------

;       Register Addresses

m_rx    EQU     $FFEF           ; Serial Receive Data Register
m_tx    EQU     $FFEF           ; Serial Transmit Data Register
m_cra   EQU     $FFEC           ; SSI Control Register A
m_crb   EQU     $FFED           ; SSI Control Register B
m_sr    EQU     $FFEE           ; SSI Status Register
m_tsr   EQU     $FFEE           ; SSI Time Slot Register

;       SSI Control Register A Bit Flags

m_pm    EQU     $FF             ; Prescale Modulus Select Mask
m_dc    EQU     $1F00           ; Frame Rate Divider Control Mask
m_wl    EQU     $6000           ; Word Length Control Mask
m_wl0   EQU     13              ; Word Length Control 0
m_wl1   EQU     14              ; Word Length Control 1
m_psr   EQU     15              ; Prescaler Range

;       SSI Control Register B Bit Flags

m_of    EQU     $3              ; Serial Output Flag Mask
m_of0   EQU     0               ; Serial Output Flag 0
m_of1   EQU     1               ; Serial Output Flag 1
m_scd   EQU     $1C             ; Serial Control Direction Mask
m_scd0  EQU     2               ; Serial Control 0 Direction
m_scd1  EQU     3               ; Serial Control 1 Direction
m_scd2  EQU     4               ; Serial Control 2 Direction
m_sckd  EQU     5               ; Clock Source Direction
m_fsl   EQU     8               ; Frame Sync Length
m_syn   EQU     9               ; Sync/Async Control
m_gck   EQU     10              ; Gated Clock Control
m_mod   EQU     11              ; Mode Select
m_ste   EQU     12              ; SSI Transmit Enable
m_sre   EQU     13              ; SSI Receive Enable
m_stie  EQU     14              ; SSI Transmit Interrupt Enable
m_srie  EQU     15              ; SSI Receive Interrupt Enable

;       SSI Status Register Bit Flags

m_if    EQU     $2              ; Serial Input Flag Mask
m_if0   EQU     0               ; Serial Input Flag 0
m_if1   EQU     1               ; Serial Input Flag 1
m_tfs   EQU     2               ; Transmit Frame Sync
m_rfs   EQU     3               ; Receive Frame Sync
m_tue   EQU     4               ; Transmitter Underrun Error
m_roe   EQU     5               ; Receiver Overrun Error
m_tde   EQU     6               ; Transmit Data Register Empty
m_rdf   EQU     7               ; Receive Data Register Full

;------------------------------------------------------------------------
;
;       EQUATES for Exception Processing
;
;------------------------------------------------------------------------

;       Register Addresses

m_ipr   EQU     $FFFF           ; Interrupt Priority Register

;       Interrupt Priority Register Bit Flags

m_ial   EQU     $7              ; IRQA Mode Mask
m_ial0  EQU     0               ; IRQA Mode Interrupt Priority Level (low)
m_ial1  EQU     1               ; IRQA Mode Interrupt Priority Level (high)
m_ial2  EQU     2               ; IRQA Mode Trigger Mode
m_ibl   EQU     $38             ; IRQB Mode Mask
m_ibl0  EQU     3               ; IRQB Mode Interrupt Priority Level (low)
m_ibl1  EQU     4               ; IRQB Mode Interrupt Priority Level (high)
m_ibl2  EQU     5               ; IRQB Mode Trigger Mode
m_hpl   EQU     $C00            ; Host Interrupt Priority Level Mask
m_hpl0  EQU     10              ; Host Interrupt Priority Level Mask (low)
m_hpl1  EQU     11              ; Host Interrupt Priority Level Mask (high)
m_ssl   EQU     $3000           ; SSI Interrupt Priority Level Mask
m_ssl0  EQU     12              ; SSI Interrupt Priority Level Mask (low)
m_ssl1  EQU     13              ; SSI Interrupt Priority Level Mask (high)
m_scl   EQU     $C000           ; SCI Interrupt Priority Level Mask
m_scl0  EQU     14              ; SCI Interrupt Priority Level Mask (low)
m_scl1  EQU     15              ; SCI Interrupt Priority Level Mask (high)