' Reflector Demo Application
'
' v.1.1 PK  20100407   Released for NAB
' v.1.2 AD  20100429   Added triple server auto selection at startup
' v.1.3 JP  20100602   Added routine 40000 gncfg=1
' v.1.4 AD  20100628   Fixed buffr reflector command to "buffer"
' v.1.5 AD  20100702   Fixed decoder stereo issue (BUG 096.01)
' v.1.6 AD  20100723   Added status informations for html.

dim ver$(32)
ver$ = "1.6 (23 Jul 2010)"

#define CFG_PORT        12345 

#define AUDIO_PORT      40000    ' local port for sending/receiving audio
#define RTP_SEND_TIME	4000		' send RTP every 4s
#define CFG_SEND_TIME	30000		' send CFG request every 30 seconds
#define CFG_TIMEOUT     300000	' no answer from the config server within 10 retries causes the audio to stop

#define BRTP_REQ_LEN    12

' variables

dim l,i,q,z    ' NON TIMER general variables

dim cfgip(2)   ' 3 addresses available for the configuration server
dim actip      ' active used ip
dim cfgid		' ID of the configuration request

' configuration answer - UDP packet
dim _Mudp$(1500)

dim udpl		   ' length of the last UDP packet received (lastlen) - see the UDP receiver
dim cfgts		' timestamp of the last configuration; 0 = no config yet
dim dst(1,1)	' streaming table for the LINK command
dim gncfg		' get new configuration; if set to 1 there is a newer configuration on the server
               ' and it should be requested
dim rstrt		' restart audio; if set to 1 the audio engine should restart with a new configuration in the

' cfg parameters
dim cfg$       ' for the configuration parser: contains a new configuration

dim brtp$(BRTP_REQ_LEN)	' BRTP request
dim mac$(13)	' contains the MAC address without separators
dim cfgto		' configuration timeout is running
dim stop       ' flag: no answer from the configuration server -> stop audio
dim stp$,tmp$
dim state

' configuration parameters from the server

#define MODE_DECODER    1
#define MODE_ENCODER    0

#define INPUT_LINE      1
#define INPUT_MIC       2
#define INPUT_OPTICAL   4
#define INPUT_COAX      5

dim mode		   ' direction of the audio communication: 0=encoder, 1=decoder
dim stere		' stere/mono
dim input		' input source

dim vol			' volume in dB
dim gain       ' input gain in dB
dim buffr		' how much to buffer on the decoder - in milliseconds
dim qual       ' MPEG encoder quality
dim rport		' replicator's port number: encoder sending audio to rport, decoder sending BRTP requests to rport+1
dim rip$(30)   ' replicator's IP address - for sending and receiving audio
dim ssrc       ' "reflector ID" - retrieved from the configuration server and send as SSRC in the encoded stream to identify the source device


' functions
' ---------

' dim gethx <gosub 5000>
' this doesn't work because BCL is broken
' workarounding with a gosub
dim ghxp1$
dim gethx

' handles
' -------
' 1 - configuration UDP socket
' 2 - audio UDP socket
' 7 - audio

#define CFG_HANDLE	   1
#define STREAM_HANDLE	2
#define AUDIO_HANDLE	   7

#define ST_STARTING		0
#define ST_RESOLVING		1
#define ST_LOOKUP			2
#define ST_DETECTED		3
#define ST_CONF_UPDATED	4
#define ST_NO_REFLE		5
#define ST_DNS_NOT_SET	6
#define ST_LINK_ON		7


' timers
' ------
' 1 - setup configuration UDP request sent every minute
' 2 - BRTP request sent every 5s
' 3 - timeout timer if no config is received

	on timer3 gosub 4000		' handle no response from the config server


	syslog "Reflector client v"+ver$

	' set up BRTP request
	mac$=sprintf$("%H",0)
	brtp$="BRX "
	' emulation of gethx(mid$(mac$,1,4)) call with gosub
	ghxp1$=mid$(mac$,1,4) : gosub 5000
	midset brtp$,5,-2,gethx

	ghxp1$=mid$(mac$,5,4) : gosub 5000
	midset brtp$,7,-2,gethx

	ghxp1$=mid$(mac$,9,4) : gosub 5000
	midset brtp$,9,-2,gethx

   ' Read server configuration

   open "STP:1200" as 0
   if mediatype(0) then
      read 0, stp$ : close 0
9
      for i=0 to 2
         q=0 : tmp$=mid$(stp$,i*32+1,32)
10	      if q<3 then
            syslog "Resolving "+tmp$
				state=ST_RESOLVING
            cfgip(i)=resolve(tmp$)
            if cfgip(i)=0 then
               syslog "Cannot resolve server address "+str$(i+1)+" : "+tmp$
					state=ST_DNS_NOT_SET
               delay 3000 : q=q+1 : goto 10
            endif
         endif
      next i
      ' server check
      for i=0 to 2
         if cfgip(i)<>0 then goto 12
      next i
		state=ST_DNS_NOT_SET
      syslog "no server IP has been resolved"
      syslog "check URL's, DNS, Gateway setting, retrying in 10 seconds ..." : delay 10000 : goto 9
	else
      syslog "can't open setup, exiting." : end
   endif

12
	' open configuration handle
	open "UDP:0.0.0.0:"+str$(CFG_PORT) as CFG_HANDLE
   ' server detection
   gosub 6000
   ' link replies now
   on UDP gosub 20000

	' setup configuration request every minute
	on timer1 gosub 11000
	gosub 11000
	timer 1,CFG_SEND_TIME

	syslog "Waiting for initial configuration"
	gncfg=1			' force configuration reload at startup
	gosub 11000



15	if rstrt=0 then goto 15
	syslog "Initial configuration received"

20
	dst(0,1)=rport
	dst(0,0)=resolve(rip$)


	rstrt=0
	stop=0

	' main loop

	if mode=MODE_DECODER then
		syslog "Initialising audio: MP3 decoder, buffering "+str$(buffr)+" milliseconds"
      ' syslog "opening audio : " + "AUD:1,1,0,"+str$(buffr)
		open "AUD:1,1,0,"+str$(buffr) as AUDIO_HANDLE
	else
		' disable CRC, disable MS stere, disable bit reservoir, emphasis none, copy, no copyright
		syslog "Initialising audio: MP3 encoder"
		' buffer is ignored, RTP payload length is "auto" and SSRC from the config server
		open "AUD:2,1,"+str$(qual+&H8700+(1-stere)*2048)+",0,0,"+str$(ssrc) as AUDIO_HANDLE
	endif

	' set audio parameters
	syslog "Configuring audio"
	write 7,str$(gain),-2		' AD gain 		' FIXME - convert dB into steps 0-15
	write 7,str$(input),-3		' input source
	'write 7,str$(stere),-4		' input mode stere/mono,  removed, see BUG 096.01
	'write 7,str$(1-stere),-5	' output mode stere/mono, removed, see BUG 096.01
	write 7,"1",-11			' dB volume
	write 7,str$(vol),-12

	open "UDP:0.0.0.0:"+str$(AUDIO_PORT) as STREAM_HANDLE

	if mode=MODE_DECODER then
		on timer2 gosub 30000
		timer 2,RTP_SEND_TIME

		' send the BRTP request 3x
		gosub 30000
		gosub 30000
		gosub 30000
	endif
	
	state=ST_LINK_ON

	syslog "Starting audio transfer"
	link STREAM_HANDLE,AUDIO_HANDLE,dst
30
	if stop<>0 then syslog "Configuration server timed out, going to idle" : goto 31
	if rstrt<>0 then syslog "New configuration received, restarting" : goto 31
	goto 30
31
	syslog "Closing audio"


' Shutdown
'---------

	' FIXME what's the proper sequence ?
	timer 2,0			' disable BRTP requests
	close STREAM_HANDLE		' stop streaming
	close AUDIO_HANDLE
	if stop then goto 15
	goto 20







' configuration request handling
'-------------------------------
11000	' send config request
	local cfgrq$
	if cfgto=0 then timer 3,CFG_TIMEOUT : cfgto=1 ': syslog "DEBUG: timeout start"

	if gncfg=1 then
		syslog "Sending configuration request"
		cfgrq$="id="+str$(cfgid)+"&mac="+mac$+"&get_config=fw_type=v.1,cfg_ver=4,set_ver=0"
		write CFG_HANDLE,cfgrq$,len(cfgrq$),sprintf$("%lA",actip),CFG_PORT
	else
		syslog "Sending ping to the configuration server"
		cfgrq$="id="+str$(cfgid)+"&mac="+mac$+"&ping"
		write CFG_HANDLE,cfgrq$,len(cfgrq$),sprintf$("%lA",actip),CFG_PORT
	endif
	cfgid=cfgid+1

	return


12000	' parse UDP answer
	' request in _Mudp$; length in udpl (negative)
	local cmd$,keyw$,pos,newts	' cmd$ - command (they are spearated with "&")
					' keyw$ - keyword within the command (before "=")
					' pos - position within the string, for string search
					' newts - new timestamp

	timer 3,0			' disable config timeout timer
	cfgto=0
	syslog "Received server response, parsing"
	midset _Mudp$,-udpl+1,1,0	' terminate the UDP string
	'syslog "DEBUG: "+_Mudp$
12001
	' split the udp answer into commands separated with "&"
	pos=instr(1,_Mudp$,"&")
	if pos=0 then
		cmd$=_Mudp$			' extract command
		_Mudp$=""			' empty _Mudp$
	else
		cmd$=mid$(_Mudp$,1,pos-1)	' extract command
		_Mudp$=mid$(_Mudp$,pos+1)	' cut off the first command
	endif

	' extract keyword from the command "cmd$"
	pos=instr(1,cmd$,"=")
	if pos=0 then
		keyw$=cmd$			' extract keyword
	else
		keyw$=mid$(cmd$,1,pos-1)	' extract keyword
	endif

	if keyw$="ping" then
		newts=val(mid$(cmd$,pos+1+len("time_stamp=")))
		if newts<>cfgts then		' new configuration available; get it
			syslog "New configuration available"
			gncfg=1
			cfgts=newts
			gosub 11000
		endif
		goto 12002
	endif
	if keyw$="status" then
		cfg$=mid$(cmd$,pos+1)
		if cfg$="no_config" then syslog "Device not configured"
		goto 12002
	endif
	if keyw$="config" then
		cfg$=mid$(cmd$,pos+1)
		gosub 12100		' parse configuration
		gncfg=0
		rstrt=1
		goto 12002
	endif
12002

	if len(_Mudp$)>0 then goto 12001
	return


12100	' parse configuration
	' request in cfg$
	local pos2,field$,keyw2$,val$,x				' position within the parsed string; configuration field; configuration keyword; value; temporary parameter

	syslog "Received a new configuration: "+cfg$
	state=ST_CONF_UPDATED
12101
	' split the config string into fields separated with ","
	pos2=instr(1,cfg$,",")
	if pos2=0 then
		field$=cfg$		' extract one cfg field
		cfg$=""			' empty cfg$
	else
		field$=mid$(cfg$,1,pos2-1)	' extract one cfg field
		cfg$=mid$(cfg$,pos2+1)		' cut off the first field
	endif

	' extract keyword from the config field
	pos2=instr(1,field$,"=")
	if pos2=0 then
		keyw2$=field$			' extract keyword
		val$=""
	else
		keyw2$=mid$(field$,1,pos2-1)	' extract keyword
		val$=mid$(field$,pos2+1)
	endif

	if keyw2$="input" then
		if val$="line" then input=INPUT_LINE
		if val$="mic" then input=INPUT_MIC
		if val$="optical" then input=INPUT_OPTICAL
		if val$="coax" then input=INPUT_COAX
		goto 12102
	endif
	if keyw2$="mode" then
		if val$="encoder" then mode=MODE_ENCODER else mode=MODE_DECODER
		goto 12102
	endif
	if keyw2$="out_gain" then
		vol=val(val$)
		goto 12102
	endif
	if keyw2$="in_gain" then
		gain=val(val$)
		goto 12102
	endif
	if keyw2$="buffer" then
		buffr=val(val$)
		goto 12102
	endif
	if keyw2$="audio_port" then
		rport=val(val$)
		goto 12102
	endif
	if keyw2$="ref_ip" then
		rip$=val$
		goto 12102
	endif
	if keyw2$="ref_id" then
		ssrc=val(val$)
		goto 12102
	endif
	if keyw2$="quality" then
		x=val(val$)
		if x=40 then stere=0 : qual=&H20	' 22.0hkHz, quality 2
		if x=64 then stere=0 : qual=&H01	' 44.1kHz, quality 0
		if x=80 then stere=1 : qual=&H31	' 44.1kHz, quality 3
		if x=140 then stere=1 : qual=&H71	' 44.1kHz, highest quality (7)
		if x=192 then stere=1 : qual=&H73	' 48kHz, highest quality (7)
		buffr=val(val$)
		goto 12102
	endif
12102

	if len(cfg$)>0 then goto 12101
	return




' UDP receive handler
'--------------------
20000
	' check config handle
	if mediatype(CFG_HANDLE)=0 then goto 20001
	udpl=lastlen(CFG_HANDLE)
	if udpl>=0 then goto 20001
	read CFG_HANDLE,_Mudp$
	gosub 12000				' parse the configuration answer
	goto 20000				' something received in the last loop, try again until the UDP FIFO is empty
20001	' discard all blocks on stream handle
	if mediatype(STREAM_HANDLE)=0 then goto 20002
	udpl=lastlen(STREAM_HANDLE)
	if udpl<0 then read STREAM_HANDLE,_Mudp$ : goto 20001
20002
	return


' BRTP send request - timer handler
'----------------------------------
30000	' send BRTP request
	write STREAM_HANDLE,brtp$,BRTP_REQ_LEN,rip$,rport
	'''syslog "Sending BRTP request"
	return


' configuration timeout - timer handler
'--------------------------------------
4000

	'syslog "DEBUG: server timed out"
	stop=1
	timer 3,0		' disable timer
	cfgto=0
	gncfg=1         ' force to send request to get configuration
	return



5000	' gethx: converts ascii hex into a 16-bit integer
	'local ghxp1$,tmp1,tmp2
	local tmp1,tmp2
	gethx=0
	ghxp1$=lcase$(ghxp1$)
	for tmp1=1 to 4
		gethx=gethx*16
		tmp2=midget(ghxp1$,tmp1,1)
		if and(tmp2>=asc("0"),tmp2<=asc("9")) then gethx=gethx+tmp2-asc("0")
		if and(tmp2>=asc("a"),tmp2<=asc("f")) then gethx=gethx+tmp2-asc("a")+10
	next tmp1
	return

6000
   ' server detection
   syslog "Reflector server detection ..."
   tmp$="id="+str$(cfgid)+"&mac="+mac$+"&ping"
	state=ST_LOOKUP
   for i=0 to 2
      ' try ping command 3 times per server
      if cfgip(i) then
         for z=0 to 3
            write CFG_HANDLE,tmp$,len(tmp$),sprintf$("%lA",cfgip(i)), CFG_PORT
            for q=0 to 10
               l=lastlen(CFG_HANDLE)
               if l<0 then
                  read CFG_HANDLE, _Mudp$
                  if instr(1,_Mudp$,"ping") then
                     syslog "Using server "+str$(i)+" : "+sprintf$("%lA",cfgip(i))
                     actip=cfgip(i)
							state=ST_DETECTED
                     return
                  endif
               endif
               delay 100
            next q
         next z
         syslog "Server "+sprintf$("%lA",cfgip(i))+" not available or device not registered in db"
      endif
   next i
	state=ST_NO_REFLE
   syslog "No Reflector service available, retrying in 10 seconds ..." : delay 10000 : goto 6000
   return

