''*************************************** ''* TV Driver v1.1 * ''* Author: Chip Gracey * ''* Copyright (c) 2004 Parallax, Inc. * ''* See end of file for terms of use. * ''*************************************** ' v1.0 - 01 May 2006 - original version ' v1.1 - 17 May 2006 - pixel tile size can now be 16 x 32 to enable more efficient ' character displays utilizing the internal font - see 'tv_mode' { 2009 May 11 Moved blank line tasks into one-time initialization section. Difficulty: one task was flipping the interlace bit of _mode, necessitating a reload of _mode every superfield. Removed the flip and changed the sense of the Z tests that depended on _mode<1> being flipped. 12 Implemented simple scrolling terminal output in background task. Difficulty: have to keep Z clear so that mainline interlaced code works properly. June 9 Added "D"isable and "E"nable commands. 20 Changing ping response to include basepin. } CON fntsc = 3_579_545 'NTSC color frequency lntsc = 3640 'NTSC color cycles per line * 16 sntsc = 624 'NTSC color cycles per sync * 16 fpal = 4_433_618 'PAL color frequency lpal = 4540 'PAL color cycles per line * 16 spal = 848 'PAL color cycles per sync * 16 paramcount = 14 ' colortable = $180 'start of colortable inside cog cols = 40 rows = 13 VAR long cog long rendezvous PUB start(basepin, rv) | okay '' Start TV driver - starts a cog if necessary '' returns true if it had to start a cog, false if cog was already running. '' _basepin := basepin rendezvous := rv long[rendezvous]~~ ' ping the sxtv cog (send -1) waitcnt( clkfreq/10 + cnt ) if long[rendezvous] <> -1 ' if the cog is alive it'll set this to _basepin<<8 long[rendezvous]~ return false _pins := (basepin & $38) << 1 | (basepin & 4 == 4) & %0101 if cog := cognew(@entry, rendezvous) + 1 return true else abort string("Couldn't start sxtv cog") PUB stop '' Stop TV driver - frees a cog if cog cogstop(cog~ - 1) PUB GetBasepin repeat while long[rendezvous] long[rendezvous]~~ ' ping the sxtv cog (send -1) repeat while long[rendezvous] == -1 return long[rendezvous]~ >> 8 PUB str(stringptr) '' Print a zero-terminated string repeat strsize(stringptr) out(byte[stringptr++]) PUB dec(value) | _i '' Print a decimal number if value < 0 -value out("-") _i := 1_000_000_000 repeat 10 if value => _i out(value / _i + "0") value //= _i result~~ elseif result or _i == 1 out("0") _i /= 10 PUB hex(value, digits) '' Print a hexadecimal number value <<= (8 - digits) << 2 repeat digits out(lookupz((value <-= 4) & $F : "0".."9", "A".."F")) PUB bin(value, digits) '' Print a binary number value <<= 32 - digits repeat digits out((value <-= 1) & 1 + "0") pub out( c ) repeat while byte[rendezvous] byte[rendezvous] := c DAT '******************************* '* Assembly language TV driver * '******************************* org ' ' ' Entry ' entry call #init mov taskptr,#tasks 'reset tasks ' ' ' Superfield ' superfield test _mode,#%0001 wc 'if ntsc, set phaseflip if_nc mov phaseflip,phasemask test _mode,#%0010 wz 'get interlace into nz mov temp, #0 wz ''' I messed something up and now Z has to be set, not clear. ' ' ' Field ' field mov x,vinv 'do invisible back porch lines :black call #hsync 'do hsync waitvid burst,sync_high2 'do black jmpret taskret,taskptr 'call task section (z undisturbed) djnz x,#:black 'another black line? ' wrlong visible,par 'set status to visible mov x,vb 'do visible back porch lines call #blank_lines mov y,_vt 'set vertical tiles movs :getchars4, #text '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< :line mov vx,_vx 'set vertical expand mov t1,#32 mov chset, chset0 :vert if_z xor interlace,#1 'interlace skip? if_z tjz interlace,#:skip call #hsync 'do hsync mov vscl,hb 'do visible back porch pixels xor tile,colortable waitvid tile,#0 mov x,_ht 'set horizontal tiles shr x, #2 'divide by 4 mov vscl,hx 'set horizontal expand :getchars4 mov chars4, 0-0 mov t3, #4 :loop4 mov char, chars4 ror chars4, #8 and char, #$ff shr char, #1 wc shl char, #7 add char, chset rdlong pixels16, char ' xor colors4, phaseflip if_nc waitvid colortable, pixels16 if_c waitvid colortable+1, pixels16 djnz t3, #:loop4 add :getchars4, #1 djnz x, #:getchars4 mov vscl,hf 'do visible front porch pixels mov tile,colortable'phaseflip ' xor tile,colortable waitvid tile,#0 sub :getchars4, #10 :skip add chset, #4 djnz t1, #:vert add :getchars4, #10 djnz y,#:line 'another tile line? if_z xor interlace,#1 wz 'get interlace and field1 into z test _mode,#%0001 wc 'do visible front porch lines mov x,vf if_nz_and_c add x,#1 call #blank_lines ' if_z wrlong invisible,par 'unless interlace and field1, set status to invisible if_z_eq_c call #hsync 'if required, do short line if_z_eq_c mov vscl,hrest if_z_eq_c waitvid burst,sync_high2 if_z_eq_c xor phaseflip,phasemask call #vsync_high 'do high vsync pulses movs vsync1,#sync_low1 'do low vsync pulses movs vsync2,#sync_low2 call #vsync_low call #vsync_high 'do high vsync pulses if_nz mov vscl,hhalf 'if odd frame, do half line if_nz waitvid burst,sync_high2 if_z jmp #field 'if interlace and field1, display field2 jmp #superfield 'else, new superfield ' ' ' Blank lines ' blank_lines call #hsync 'do hsync xor tile,colortable 'do background waitvid tile,#0 djnz x,#blank_lines blank_lines_ret ret ' ' ' Horizontal sync ' hsync test _mode,#%0001 wc 'if pal, toggle phaseflip ' if_c xor phaseflip,phasemask mov vscl,sync_scale1 'do hsync mov tile,burst'phaseflip ' xor tile,burst waitvid tile,sync_normal mov vscl,hvis 'setup in case blank line mov tile,#0'phaseflip hsync_ret ret ' ' ' Vertical sync ' vsync_high movs vsync1,#sync_high1 'vertical sync movs vsync2,#sync_high2 vsync_low mov x,vrep vsyncx mov vscl,sync_scale1 vsync1 waitvid burst,sync_high1 mov vscl,sync_scale2 vsync2 waitvid burst,sync_high2 djnz x,#vsyncx vsync_low_ret vsync_high_ret ret ' ' ' Tasks - performed in sections during invisible back porch lines ' tasks clearscreen mov i, #14 ' clear 13 lines + 1 extra to make scrolling easier :lines mov j, #40/4 ' 40 columns, 4 chars at a time :chars :storem mov text, x20202020 add :storem, d0 ' inc dst djnz j, #:chars jmpret taskptr,taskret djnz i, #:lines waitforchar mov temp, #0 ' clear buffer: ready to accept next byte wrlong temp, par waitnoclear :wait jmpret taskptr,taskret rdlong ch, par tjz ch, #:wait ' got a byte mov temp, cmdPing ' roundabout comparisons to avoid changing Z. sub temp, ch ' ch = -1 is just a ping to see if this cog is alive. tjz temp, #ping mov temp, cmdDisable sub temp, ch tjz temp, #disable mov temp, cmdEnable sub temp, ch tjz temp, #enable neg temp, #256 ' at this point, if ch is > 255, it must be _basepin<<8 and temp, ch ' as set by a previous ping, so we just ignore it tjnz temp, #waitnoclear ' (leave the long pointed to by par undisturbed). :char mov temp, #8 sub temp, ch tjz temp, #:bksp mov temp, #13 sub temp, ch tjnz temp, #:printchar call #cr jmp #waitforchar :bksp sub col, #1 ' warning: no error-checking jmp #waitforchar :printchar mov temp, #40 sub temp, col tjnz temp, #:nocr call #cr :nocr mov temp, col shr temp, #2 add temp, #text+12*40/4 movs :get4chars, temp movd :put4chars, temp mov i, col and i, #3 shl i, #3 ' col&3 => 0, 8, 16, 24 :get4chars mov temp, 0-0 ror temp, i andn temp, #$ff or temp, ch rol temp, i :put4chars mov 0-0, temp add col, #1 jmp #waitforchar cr movs :move4chars, #text + 40 / 4 movd :move4chars, #text mov i, #13 ' copy 13 lines up. Since we have an extra blank line at the bottom ' (see clearscreen) the last visible line is automatically cleared. :copyline mov j, #40/4 :move4chars mov 0-0, 0-0 add :move4chars, d0s0 djnz j, #:move4chars jmpret taskptr,taskret djnz i, #:copyline mov col, #0 cr_ret ret ping mov temp, _basepin shl temp, #8 wrlong temp, par jmp #waitnoclear enable mov dira, saveDira mov dirb, saveDirb jmp #waitforchar disable mov dira, #0 mov dirb, #0 jmp #waitforchar ' ' ' Initialized data ' x20202020 long $20202020 chset0 long $8000 m8 long 8_000_000 m128 long 128_000_000 d0 long 1 << 9 << 0 d6 long 1 << 9 << 6 d0s0 long 1 << 9 << 0 + 1 << 0 d0s1 long 1 << 9 << 0 + 1 << 1 interlace long 0 invisible long 1 visible long 2 phaseflip long $00000000 phasemask long $F0F0F0F0 line long $00060000 lineinc long $10000000 linerot long 0 pins0 long %11110000_01110000_00001111_00000111 pins1 long %11111111_11110111_01111111_01110111 sync_high1 long %0101010101010101010101_101010_0101 sync_high2 long %01010101010101010101010101010101 'used for black sync_low1 long %1010101010101010101010101010_0101 sync_low2 long %01_101010101010101010101010101010 colortable ' long $07_0a_07_0a ' white on blue ' long $07_07_0a_0a ' long $bc_02_bc_02 ' green on black ' long $bc_bc_02_02 ' long $05_02_05_02 ' white on black ' long $05_05_02_02 long $02_05_02_05 ' black on white long $02_02_05_05 ' long $8a_8c_8a_8c ' black on yellow ' long $8a_8a_8c_8c ' ' ' NTSC/PAL metrics tables ' ntsc pal ' ---------------------------------------------- wtab word lntsc - sntsc, lpal - spal 'hvis word lntsc / 2 - sntsc, lpal / 2 - spal 'hrest word lntsc / 2, lpal / 2 'hhalf word 243, 286 'vvis word 10, 18 'vinv word 6, 5 'vrep word $02_8A, $02_AA 'burst wtabx ltab long fntsc 'fcolor long fpal long sntsc >> 4 << 12 + sntsc 'sync_scale1 long spal >> 4 << 12 + spal long 67 << 12 + lntsc / 2 - sntsc 'sync_scale2 long 79 << 12 + lpal / 2 - spal long %0101_00000000_01_10101010101010_0101 'sync_normal long %010101_00000000_01_101010101010_0101 ltabx cmdPing long -1 cmdEnable long "E"<<8 cmdDisable long "D"<<8 _basepin long 0 ' ' ' Parameter buffer ' _enable long 1 'enable _pins long 0 'pins _mode long %10010 'mode _screen long 0 'screen _colors long 0 'colors _ht long cols 'hc _vt long rows 'vc _hx long 4 'hx _vx long 1 'vx _ho long 0 'ho _vo long 0 'vo _broadcast long 0 'broadcast _auralcog long 0 'auralcog ' ' ' Uninitialized data ' i long 0 j long 0 col long 0 ch long 0 temp long 0 char long 0 chset long 0 chars4 long 0 pixels16 long 0 taskptr long 0 'tasks taskret long 0 t1 long 0 t2 long 0 t3 long 0 m1 long 0 m2 long 0 x long 0 'display y long 0 hf long 0 hb long 0 vf long 0 vb long 0 hx long 0 vx long 0 hc2x long 0 screen long 0 tile long 0 pixels long 0 lineadd long 0 hvis long 0 'loaded from word table hrest long 0 hhalf long 0 vvis long 0 vinv long 0 vrep long 0 burst long 0 fcolor long 0 'loaded from long table sync_scale1 long 0 sync_scale2 long 0 sync_normal long 0 saveDira long 0 saveDirb long 0 text ' Cog memory from this point on will be reclaimed as text buffer. init mov t1,_pins 'set video pins and directions test t1,#$08 wc if_nc mov t2,pins0 if_c mov t2,pins1 test t1,#$40 wc shr t1,#1 shl t1,#3 shr t2,t1 movs vcfg,t2 shr t1,#6 movd vcfg,t1 shl t1,#3 and t2,#$FF shl t2,t1 if_nc mov dira,t2 if_nc mov saveDira, t2 if_nc mov dirb,#0 if_nc mov saveDirb, #0 if_c mov dira,#0 if_c mov saveDira, #0 if_c mov dirb,t2 '+18 if_c mov saveDirb, t2 movs :rd,#wtab 'load ntsc/pal metrics from word table movd :wr,#hvis mov t1,#wtabx - wtab test _mode,#%0001 wc :rd mov t2,0 add :rd,#1 if_nc shl t2,#16 shr t2,#16 :wr mov 0,t2 add :wr,d0 djnz t1,#:rd '+54 if_nc movs :ltab,#ltab 'load ntsc/pal metrics from long table if_c movs :ltab,#ltab+1 movd :ltab,#fcolor mov t1,#(ltabx - ltab) >> 1 :ltab mov 0,0 add :ltab,d0s1 djnz t1,#:ltab '+17 rdlong t1,#0 'get CLKFREQ shr t1,#1 'if CLKFREQ < 16MHz, cancel _broadcast cmp t1,m8 wc if_c mov _broadcast,#0 shr t1,#1 'if CLKFREQ < color frequency * 4, disable ' cmp t1,fcolor wc ' if_c jmp #disabled '+11 mov t1,fcolor 'set ctra pll to fcolor * 16 call #divide 'if ntsc, set vco to fcolor * 32 (114.5454 MHz) test _mode,#%0001 wc 'if pal, set vco to fcolor * 16 (70.9379 MHz) if_c movi ctra,#%00001_111 'select fcolor * 16 output (ntsc=/2, pal=/1) if_nc movi ctra,#%00001_110 if_nc shl t2,#1 mov frqa,t2 '+147 mov t1,_broadcast 'set ctrb pll to _broadcast mov t2,#0 'if 0, turn off ctrb tjz t1,#:off min t1,m8 'limit from 8MHz to 128MHz max t1,m128 mov t2,#%00001_100 'adjust _broadcast to be within 4MHz-8MHz :scale shr t1,#1 '(vco will be within 64MHz-128MHz) cmp m8,t1 wc if_c add t2,#%00000_001 if_c jmp #:scale :off movi ctrb,t2 call #divide mov frqb,t2 '+165 mov t1,#%10100_000 'set video configuration test _pins,#$01 wc '(swap broadcast/baseband output bits?) if_c or t1,#%01000_000 test _mode,#%1000 wc '(strip chroma from broadcast?) if_nc or t1,#%00010_000 test _mode,#%0100 wc '(strip chroma from baseband?) if_nc or t1,#%00001_000 and _auralcog,#%111 '(set aural cog) or t1,_auralcog movi vcfg,t1 '+10 mov hx,_hx 'compute horizontal metrics shl hx,#8 or hx,_hx shl hx,#4 mov hc2x,_ht shl hc2x,#1 mov t1,_ht mov t2,_hx call #multiply mov hf,hvis sub hf,t1 shr hf,#1 wc mov hb,_ho addx hb,hf sub hf,_ho '+52 mov t1,_vt 'compute vertical metrics mov t2,_vx call #multiply test _mode,#%10000 wc 'consider tile size muxc linerot,#1 mov lineadd,lineinc if_c shr lineadd,#1 if_c shl t1,#1 test _mode,#%0010 wc 'consider interlace if_c shr t1,#1 mov vf,vvis sub vf,t1 shr vf,#1 wc neg vb,_vo addx vb,vf add vf,_vo '+53 init_ret ret ' ' ' Divide t1/CLKFREQ to get frqa or frqb value into t2 ' divide rdlong m1,#0 'get CLKFREQ mov m2,#32+1 :loop cmpsub t1,m1 wc rcl t2,#1 shl t1,#1 djnz m2,#:loop divide_ret ret '+140 ' ' ' Multiply t1 * t2 * 16 (t1, t2 = bytes) ' multiply shl t2,#8+4-1 mov m1,#8 :loop shr t1,#1 wc if_c add t1,t2 djnz m1,#:loop multiply_ret ret '+37 '' ''___ ''VAR 'TV parameters - 14 contiguous longs '' '' long tv_status '0/1/2 = off/invisible/visible read-only '' long tv_enable '0/non-0 = off/on write-only '' long tv_pins '%pppmmmm = pin group, pin group mode write-only '' long tv_mode '%tccip = tile,chroma,interlace,ntsc/pal write-only '' long tv_screen 'pointer to screen (words) write-only '' long tv_colors 'pointer to colors (longs) write-only '' long tv_ht 'horizontal tiles write-only '' long tv_vt 'vertical tiles write-only '' long tv_hx 'horizontal tile expansion write-only '' long tv_vx 'vertical tile expansion write-only '' long tv_ho 'horizontal offset write-only '' long tv_vo 'vertical offset write-only '' long tv_broadcast 'broadcast frequency (Hz) write-only '' long tv_auralcog 'aural fm cog write-only '' ''The preceding VAR section may be copied into your code. ''After setting variables, do start(@tv_status) to start driver. '' ''All parameters are reloaded each superframe, allowing you to make live ''changes. To minimize flicker, correlate changes with tv_status. '' ''Experimentation may be required to optimize some parameters. '' ''Parameter descriptions: '' _________ '' tv_status '' '' driver sets this to indicate status: '' 0: driver disabled (tv_enable = 0 or CLKFREQ < requirement) '' 1: currently outputting invisible sync data '' 2: currently outputting visible screen data '' _________ '' tv_enable '' '' 0: disable (pins will be driven low, reduces power) '' non-0: enable '' _______ '' tv_pins '' '' bits 6..4 select pin group: '' %000: pins 7..0 '' %001: pins 15..8 '' %010: pins 23..16 '' %011: pins 31..24 '' %100: pins 39..32 '' %101: pins 47..40 '' %110: pins 55..48 '' %111: pins 63..56 '' '' bits 3..0 select pin group mode: '' %0000: %0000_0111 - baseband '' %0001: %0000_0111 - broadcast '' %0010: %0000_1111 - baseband + chroma '' %0011: %0000_1111 - broadcast + aural '' %0100: %0111_0000 broadcast - '' %0101: %0111_0000 baseband - '' %0110: %1111_0000 broadcast + aural - '' %0111: %1111_0000 baseband + chroma - '' %1000: %0111_0111 broadcast baseband '' %1001: %0111_0111 baseband broadcast '' %1010: %0111_1111 broadcast baseband + chroma '' %1011: %0111_1111 baseband broadcast + aural '' %1100: %1111_0111 broadcast + aural baseband '' %1101: %1111_0111 baseband + chroma broadcast '' %1110: %1111_1111 broadcast + aural baseband + chroma '' %1111: %1111_1111 baseband + chroma broadcast + aural '' ----------------------------------------------------------- '' active pins top nibble bottom nibble '' '' the baseband signal nibble is arranged as: '' bit 3: chroma signal for s-video (attach via 560-ohm resistor) '' bits 2..0: baseband video (sum 270/560/1100-ohm resistors to form 75-ohm 1V signal) '' '' the broadcast signal nibble is arranged as: '' bit 3: aural subcarrier (sum 560-ohm resistor into network below) '' bits 2..0: visual carrier (sum 270/560/1100-ohm resistors to form 75-ohm 1V signal) '' _______ '' tv_mode '' '' bit 4 selects between 16x16 and 16x32 pixel tiles: '' 0: 16x16 pixel tiles (tileheight = 16) '' 1: 16x32 pixel tiles (tileheight = 32) '' '' bit 3 controls chroma mixing into broadcast: '' 0: mix chroma into broadcast (color) '' 1: strip chroma from broadcast (black/white) '' '' bit 2 controls chroma mixing into baseband: '' 0: mix chroma into baseband (composite color) '' 1: strip chroma from baseband (black/white or s-video) '' '' bit 1 controls interlace: '' 0: progressive scan (243 display lines for NTSC, 286 for PAL) '' less flicker, good for motion '' 1: interlaced scan (486 display lines for NTSC, 572 for PAL) '' doubles the vertical display lines, good for text '' '' bit 0 selects NTSC or PAL format '' 0: NTSC '' 3016 horizontal display ticks '' 243 or 486 (interlaced) vertical display lines '' CLKFREQ must be at least 14_318_180 (4 * 3_579_545 Hz)* '' 1: PAL '' 3692 horizontal display ticks '' 286 or 572 (interlaced) vertical display lines '' CLKFREQ must be at least 17_734_472 (4 * 4_433_618 Hz)* '' '' * driver will disable itself while CLKFREQ is below requirement '' _________ '' tv_screen '' '' pointer to words which define screen contents (left-to-right, top-to-bottom) '' number of words must be tv_ht * tv_vt '' each word has two bitfields: a 6-bit colorset ptr and a 10-bit pixelgroup ptr '' bits 15..10: select the colorset* for the associated pixel tile '' bits 9..0: select the pixelgroup** address %ppppppppppcccc00 (p=address, c=0..15) '' '' * colorsets are longs which each define four 8-bit colors '' '' ** pixelgroups are longs which define (left-to-right, top-to-bottom) the 2-bit '' (four color) pixels that make up a 16x16 or a 32x32 pixel tile '' _________ '' tv_colors '' '' pointer to longs which define colorsets '' number of longs must be 1..64 '' each long has four 8-bit fields which define colors for 2-bit (four color) pixels '' first long's bottom color is also used as the screen background color '' 8-bit color fields are as follows: '' bits 7..4: chroma data (0..15 = blue..green..red..)* '' bit 3: controls chroma modulation (0=off, 1=on) '' bits 2..0: 3-bit luminance level: '' values 0..1: reserved for sync - don't use '' values 2..7: valid luminance range, modulation adds/subtracts 1 (beware of 7) '' value 0 may be modulated to produce a saturated color toggling between levels 1 and 7 '' '' * because of TV's limitations, it doesn't look good when chroma changes abruptly - '' rather, use luminance - change chroma only against a black or white background for '' best appearance '' _____ '' tv_ht '' '' horizontal number pixel tiles - must be at least 1 '' practical limit is 40 for NTSC, 50 for PAL '' _____ '' tv_vt '' '' vertical number of pixel tiles - must be at least 1 '' practical limit is 13 for NTSC, 15 for PAL (26/30 max for interlaced NTSC/PAL) '' _____ '' tv_hx '' '' horizontal tile expansion factor - must be at least 3 for NTSC, 4 for PAL '' '' make sure 16 * tv_ht * tv_hx + ||tv_ho + 32 is less than the horizontal display ticks '' _____ '' tv_vx '' '' vertical tile expansion factor - must be at least 1 '' '' make sure * tv_vt * tv_vx + ||tv_vo + 1 is less than the display lines '' _____ '' tv_ho '' '' horizontal offset in ticks - pos/neg value (0 for centered image) '' shifts the display right/left '' _____ '' tv_vo '' '' vertical offset in lines - pos/neg value (0 for centered image) '' shifts the display up/down '' ____________ '' tv_broadcast '' '' broadcast frequency expressed in Hz (ie channel 2 is 55_250_000) '' if 0, modulator is turned off - saves power '' '' broadcasting requires CLKFREQ to be at least 16_000_000 '' while CLKFREQ is below 16_000_000, modulator will be turned off '' ___________ '' tv_auralcog '' '' selects cog to supply aural fm signal - 0..7 '' uses ctra pll output from selected cog '' '' in NTSC, the offset frequency must be 4.5MHz and the max bandwidth +-25KHz '' in PAL, the offset frequency and max bandwidth vary by PAL type {{ +------------------------------------------------------------------------------------------------------------------------------+ ¦ TERMS OF USE: MIT License ¦ +------------------------------------------------------------------------------------------------------------------------------¦ ¦Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation ¦ ¦files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, ¦ ¦modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software¦ ¦is furnished to do so, subject to the following conditions: ¦ ¦ ¦ ¦The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.¦ ¦ ¦ ¦THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE ¦ ¦WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR ¦ ¦COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ¦ ¦ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ¦ +------------------------------------------------------------------------------------------------------------------------------+ }}