Mercurial > hg > nodescore
comparison nodescore.js @ 42:49c94f63b8b0
css for nexus 7 and associated files- archive m.a added - remove later
author | tzara <rc-web@kiben.net> |
---|---|
date | Tue, 04 Sep 2012 07:25:49 +0000 |
parents | 56767c69b7c4 |
children | 7f0485e0d0ff |
comparison
equal
deleted
inserted
replaced
41:56767c69b7c4 | 42:49c94f63b8b0 |
---|---|
12 , http = require('http') | 12 , http = require('http') |
13 , fs = require('fs') | 13 , fs = require('fs') |
14 , $ = require('jQuery') | 14 , $ = require('jQuery') |
15 , static = require('node-static'); | 15 , static = require('node-static'); |
16 | 16 |
17 var bla | |
18 var pinging=0 | 17 var pinging=0 |
19 console.log("ping set to 0") | 18 |
19 console.log("ping set to 0") | |
20 | 20 |
21 // run webserver serving static html | 21 // run webserver serving static html |
22 //////////////////////////////////////////// | 22 //////////////////////////////////////////// |
23 | 23 |
24 var clientFiles = new static.Server('./www'); | 24 var clientFiles = new static.Server('./www'); |
25 | |
25 var httpServer = http.createServer(function(request, response) { | 26 var httpServer = http.createServer(function(request, response) { |
26 request.addListener('end', function () { | 27 request.addListener('end', function () { |
27 clientFiles.serve(request, response); | 28 clientFiles.serve(request, response); |
28 process.setMaxListeners(0); | 29 process.setMaxListeners(0); |
29 }); | 30 }); |
30 }); | 31 }); |
32 | |
31 httpServer.listen(8889); | 33 httpServer.listen(8889); |
32 | 34 |
33 //////////////////////////////////////////// | 35 //////////////////////////////////////////// |
34 // connect to websockets | 36 // connect to websockets |
35 //////////////////////////////////////////// | 37 //////////////////////////////////////////// |
36 | 38 |
37 var io = sio.listen(httpServer) | 39 var io = sio.listen(httpServer) |
38 , nicknames = {}; | 40 , nicknames = {}; |
39 | 41 |
40 io.set('log level', 1); // reduce loggingi | 42 io.set('log level', 1); // reduce logging |
43 | |
41 io.sockets.on('connection', function (socket) { | 44 io.sockets.on('connection', function (socket) { |
42 | 45 |
43 socket.on('nickname', function (nick, fn) { | 46 socket.on('nickname', function (nick, fn) { |
44 if (nicknames[nick]) { | 47 if (nicknames[nick]) { |
45 fn(true); | 48 fn(true); |
46 } else { | 49 } else { |
47 fn(false); | 50 fn(false); |
48 nicknames[nick] = socket.nickname = nick; | 51 nicknames[nick] = socket.nickname = nick; |
49 socket.broadcast.emit('announcement', nick + ' connected'); | 52 socket.broadcast.emit('announcement', nick + ' connected'); |
50 io.sockets.emit('nicknames', nicknames); | 53 io.sockets.emit('nicknames', nicknames); |
51 } | 54 } |
52 }); | 55 }); |
53 | 56 |
54 | 57 /// chat user messages to screens and to log file |
55 socket.on('disconnect', function(client) { | 58 // date format for filename |
56 | 59 |
57 if (!socket.nickname) return; | 60 var da = new Date(); var dtstring = da.getFullYear()+ '-' + da.getMonth()+ '-' + da.getDate(); |
58 | 61 |
59 delete nicknames[socket.nickname]; | 62 //////////////////////// |
60 socket.broadcast.emit('announcement', socket.nickname + ' disconnected'); | 63 /// log messages to file |
61 socket.broadcast.emit('nicknames', nicknames); | 64 socket.on('user message', function (msg) { |
65 fs.open('logs/chatlog-'+dtstring+'.txt', 'a', 666, function( e, id ) { | |
66 //time format for message stamp | |
67 var dt = new Date();var timestring = dt.getHours() + ':' + dt.getMinutes() + ':' + dt.getSeconds(); | |
68 | |
69 socket.broadcast.emit('user message', socket.nickname, msg); | |
70 | |
71 var fs = require('fs'), str = msg; | |
72 fs.write( id, timestring+" " + socket.nickname + ": "+ msg+"\n", null, 'utf8', function(){}); | |
73 }); | |
62 }); | 74 }); |
63 | 75 |
64 | |
65 | |
66 /// chat user messages to screens and to log file | |
67 | |
68 var da = new Date(); | |
69 var dtstring = da.getFullYear() | |
70 + '-' + da.getMonth() | |
71 + '-' + da.getDate() | |
72 ; | |
73 | |
74 fs.open('logs/chatlog-'+dtstring+'.txt', 'a', 666, function( e, id ) { | |
75 | |
76 socket.on('user message', function (msg) { | |
77 var dt = new Date();var timestring = dt.getHours() + ':' + dt.getMinutes() + ':' + dt.getSeconds(); | |
78 socket.broadcast.emit('user message', socket.nickname, msg); | |
79 | |
80 //////////////////////// | |
81 /// log messages to file | |
82 | |
83 var fs = require('fs'), str = msg; | |
84 fs.write( id, timestring+" " + socket.nickname + ": "+ msg+"\n", null, 'utf8', function(){}); | |
85 }); | |
86 | |
87 }); | |
88 | |
89 | |
90 //////////////////////////////////////////// | 76 //////////////////////////////////////////// |
91 // metronome | 77 // metronome |
92 //////////////////////////////////////////// | 78 //////////////////////////////////////////// |
79 | |
93 socket.on('metroStop', stopMetro) | 80 socket.on('metroStop', stopMetro) |
94 function stopMetro () { | 81 function stopMetro () { |
95 metroState=0; | 82 metroState=0; |
96 }; | 83 }; |
97 | 84 |
98 | |
99 //////////////////////////////////////////// | 85 //////////////////////////////////////////// |
100 // Chronometer | 86 // Chronometer |
101 //////////////////////////////////////////// | 87 //////////////////////////////////////////// |
102 | 88 |
103 // number padding: 0 to 00 | 89 // number padding: 0 to 00 |
105 function pad(number) { return (number < 10 ? '0' : '') + number } | 91 function pad(number) { return (number < 10 ? '0' : '') + number } |
106 | 92 |
107 // the chronometer initial states | 93 // the chronometer initial states |
108 var chronstate = 0; var zecsec = 0; var seconds = 0; | 94 var chronstate = 0; var zecsec = 0; var seconds = 0; |
109 var mins = 0; var hours = 0; | 95 var mins = 0; var hours = 0; |
110 | |
111 function startChr() { if (chronstate !== 1) { | |
112 chronstate = 1; chronometer();} | |
113 } // if not already started start the chronometer | |
114 | |
115 function stopChr() { chronstate = 0; } | |
116 // stop the chronometer | |
117 | |
118 function resetChr() {//clearInterval(); | |
119 chronstate = 0; | |
120 zecsec = 0; seconds = 0; | |
121 mins = 0; hours = 0; | |
122 chronstate = 0; | |
123 var chron = pad(hours) +":"+pad(mins)+ ':'+ pad(seconds)+ ":"+ zecsec | |
124 // send 0.0.0 values to display | |
125 socket.broadcast.emit('chronFromServer', chron) | |
126 socket.emit('chronFromServer', chron) | |
127 } | |
128 | 96 |
129 var dater | 97 var dater |
130 function dateTime() { | 98 function dateTime() { |
131 dater = setInterval( function () { | 99 dater = setInterval( function () { |
132 var datetime= new Date(); | 100 var datetime= new Date(); |
139 dateTime() | 107 dateTime() |
140 | 108 |
141 function chronometer() { | 109 function chronometer() { |
142 | 110 |
143 if (chronstate==1){ | 111 if (chronstate==1){ |
144 zecsec += 1; // set tenths of a second | 112 zecsec += 1; // set tenths of a second |
145 if(zecsec > 9) { zecsec = 0; seconds += 1;} | 113 if(zecsec > 9) { zecsec = 0; seconds += 1;} |
146 if(seconds > 59) { seconds = 0;mins += 1;} | 114 if(seconds > 59) { seconds = 0;mins += 1;} |
147 if(mins > 59) { mins = 0; hours += 1; } | 115 if(mins > 59) { mins = 0; hours += 1; } |
148 var chron = pad(hours) +":"+pad(mins)+ ':'+ pad(seconds)+ ":"+ zecsec | 116 var chron = pad(hours) +":"+pad(mins)+ ':'+ pad(seconds)+ ":"+ zecsec |
149 setTimeout(function(){chronometer()}, 100); | 117 setTimeout(function(){chronometer()}, 100); |
150 socket.broadcast.emit('chronFromServer', chron) | 118 socket.broadcast.emit('chronFromServer', chron) |
151 socket.emit('chronFromServer', chron) | 119 socket.emit('chronFromServer', chron) |
152 } | 120 } |
153 } | 121 } |
154 | 122 |
155 socket.on('startChr', function () { startChr();}); | 123 socket.on('startChr', function () { startChr();}); |
156 socket.on('stopChr', function () { stopChr();} ); | 124 function startChr() { if (chronstate !== 1) { |
125 chronstate = 1; chronometer();} | |
126 } // if not already started start the chronometer | |
127 | |
128 socket.on('stopChr', function () { stopChr();} ); | |
129 function stopChr() { chronstate = 0; } | |
130 // stop the chronometer | |
131 | |
157 socket.on('resetChr', function () { resetChr();}); | 132 socket.on('resetChr', function () { resetChr();}); |
158 | 133 function resetChr() {//clearInterval(); |
159 | 134 chronstate = 0; |
160 | 135 zecsec = 0; seconds = 0; |
161 | 136 mins = 0; hours = 0; |
137 chronstate = 0; | |
138 var chron = pad(hours) +":"+pad(mins)+ ':'+ pad(seconds)+ ":"+ zecsec | |
139 // send 0.0.0 values to display | |
140 socket.broadcast.emit('chronFromServer', chron) | |
141 socket.emit('chronFromServer', chron) | |
142 } | |
143 | |
162 //////////////////////////////////////////// | 144 //////////////////////////////////////////// |
163 // magic square sequencer | 145 // magic square sequencer |
164 //////////////////////////////////////////// | 146 //////////////////////////////////////////// |
165 | 147 |
166 var sequencerState = 0; | 148 var sequencerState = 0; |
167 var timemultiplier=1000 | 149 |
168 var srcsqr = [] | 150 var srcsqr = [] |
169 | |
170 srcsqr[0] = [22,21,24,25,06,07]; | 151 srcsqr[0] = [22,21,24,25,06,07]; |
171 srcsqr[1] = [20,23,27,26,05,04]; | 152 srcsqr[1] = [20,23,27,26,05,04]; |
172 srcsqr[2] = [03,00,17,16,35,34]; | 153 srcsqr[2] = [03,00,17,16,35,34]; |
173 srcsqr[3] = [01,02,19,18,33,32]; | 154 srcsqr[3] = [01,02,19,18,33,32]; |
174 | 155 |
175 var seqA = { metrobeat:0, voice:1, name: "A", counter: 0, mm: 60, beatsinbar: 4, durations: srcsqr[0], units: [1,2,3,1,2,4]}; | 156 var seqA = { metrobeat:0, voice:1, name: "A", counter: 0, mm: 120, beatsinbar: 4, durations: srcsqr[0], units: [1,2,3,1,2,4]}; |
176 var seqB = { metrobeat:0, voice:2, name: "B", counter: 0, mm: 60, beatsinbar: 4, durations: srcsqr[1], units: [3,2,1,1,2,3]}; | 157 var seqB = { metrobeat:0, voice:2, name: "B", counter: 0, mm: 120, beatsinbar: 4, durations: srcsqr[1], units: [3,2,1,1,2,3]}; |
177 var seqC = { metrobeat:0, voice:3, name: "C", counter: 0, mm: 60, beatsinbar: 4, durations: srcsqr[2], units: [4,5,6,5,4,2]}; | 158 var seqC = { metrobeat:0, voice:3, name: "C", counter: 0, mm: 120, beatsinbar: 4, durations: srcsqr[2], units: [4,5,6,5,4,2]}; |
178 var seqD = { metrobeat:0, voice:4, name: "D", counter: 0, mm: 60, beatsinbar: 4, durations: srcsqr[3], units: [2,3,4,2,3,1]}; | 159 var seqD = { metrobeat:0, voice:4, name: "D", counter: 0, mm: 120, beatsinbar: 4, durations: srcsqr[3], units: [2,3,4,2,3,1]}; |
179 | 160 |
180 | |
181 var countdowntick = function(seq){ | 161 var countdowntick = function(seq){ |
182 | 162 |
183 var outcount = 12; var incount=12; | 163 var tempoms = 60000/seq.mm |
184 var time = ((seq.durations[seq.counter]+1) *timemultiplier) + 30000 + (outcount*1000); | 164 var timemultiplier=tempoms |
185 var ztime = time; var totaltime = time/1000 | 165 var outcount = 12; var incount=12; |
186 var unit = seq.units[seq.counter]; | 166 var time = ((seq.durations[seq.counter]+1) *timemultiplier) + 30000 + (outcount*tempoms); |
187 var tempoms = 60000/seq.mm | 167 var ztime = time; var totaltime = time/tempoms |
188 | 168 var unit = seq.units[seq.counter]; |
189 // initiate first page here | 169 |
190 socket.broadcast.emit("pageFlipfromserver", seq.voice, unit, time, seq.mm,seq.counter); | 170 // initiate first page here |
191 | 171 socket.broadcast.emit("pageFlipfromserver", seq.voice, unit, time, seq.mm,seq.counter); |
192 function sequenCer() { | 172 socket.emit("pageFlipfromserver", seq.voice, unit, time, seq.mm,seq.counter); |
193 | 173 |
194 if (ztime >= 0 ){ | 174 function sequenCer() { |
195 | 175 |
176 if (ztime >= 0 ){ | |
177 | |
196 // basic unit is still the second/1000ms - change this to tempoms? no i dont think so | 178 // basic unit is still the second/1000ms - change this to tempoms? no i dont think so |
197 // count in and count out | 179 // count in and count out |
198 //////////////////////////////////////////// | 180 //////////////////////////////////////////// |
199 | 181 |
200 var counter = ztime/1000 | 182 var counter = ztime/tempoms |
201 // | 183 // |
202 if (counter >= 0 ){ | 184 if (counter >= 0 ){ |
203 socket.broadcast.emit('counterText', seq.voice, seq.counter, counter); | 185 socket.broadcast.emit('counterText', seq.voice, seq.counter, counter); |
204 socket.emit('counterText', seq.voice, seq.counter, counter); | 186 socket.emit('counterText', seq.voice, seq.counter, counter); |
205 | 187 |
206 if (counter <= outcount ) { | 188 if (counter <= outcount ) { |
207 socket.broadcast.emit('countinFromServer', seq.voice, counter, "","stop in:", "red", "transparent"); | 189 socket.broadcast.emit('countinFromServer', seq.voice, counter, "","stop in: ", "red", "transparent"); |
208 } | 190 } |
209 | 191 |
210 if (counter > (totaltime)-incount && counter <= totaltime ) { | 192 if (counter > (totaltime)-incount && counter <= totaltime ) { |
211 socket.broadcast.emit('countinFromServer', seq.voice, counter-(totaltime-incount), "","play in:", "green","transparent"); | 193 socket.broadcast.emit('countinFromServer', seq.voice, counter-(totaltime-incount), "","play in: ", "green","transparent"); |
212 socket.emit('countinFromServer', seq.voice, counter-(totaltime-incount), "","play in:", "green","transparent"); | 194 socket.emit('countinFromServer', seq.voice, counter-(totaltime-incount), "","play in: ", "green","transparent"); |
213 | 195 |
214 } | 196 } |
215 | 197 |
216 if (counter == (totaltime)-incount ) { | 198 if (counter == (totaltime)-incount ) { |
217 socket.broadcast.emit('countinFromServer', seq.voice, "+", "","playing..", "green","black"); | 199 socket.broadcast.emit('countinFromServer', seq.voice, "+", "","playing.. ", "green","transparent"); |
218 socket.emit('countinFromServer', seq.voice, "+", "","playing..", "green","black"); | 200 socket.emit('countinFromServer', seq.voice, "+", "","playing.. ", "green","transparent"); |
219 } | 201 } |
220 | 202 |
221 // remove displayed number with " " at end of both countin/out | 203 // remove displayed number with " " at end of both countin/out |
222 | 204 |
223 | 205 if (counter == 0 ) { |
224 | 206 socket.broadcast.emit('countinFromServer', seq.voice, "", "","", "green","transparent"); |
225 if (counter == 0 ) { | 207 socket.broadcast.emit('counterText', seq.voice, seq.counter, ""); |
226 socket.broadcast.emit('countinFromServer', seq.voice, "", "","", "green","transparent"); | 208 socket.emit('counterText', seq.voice, seq.counter, ""); |
227 socket.broadcast.emit('counterText', seq.voice, seq.counter, ""); | 209 } |
228 socket.emit('counterText', seq.voice, seq.counter, ""); | 210 } |
229 } | 211 |
230 } | 212 // on each beat do: |
231 | 213 |
232 | 214 // push out the pulse to metronome |
233 // on each beat do: | 215 seq.metrobeat = (seq.metrobeat+1)%seq.beatsinbar ; |
234 | 216 socket.broadcast.emit('metroPulse', tempoms, seq.voice,seq.metrobeat); |
235 // push out the pulse to metronome | 217 socket.emit('metroPulse', tempoms, seq.voice,seq.metrobeat); |
236 seq.metrobeat = (seq.metrobeat+1)%seq.beatsinbar ; | 218 } |
237 socket.broadcast.emit('metroPulse', tempoms, seq.voice,seq.metrobeat); | 219 |
238 socket.emit('metroPulse', tempoms, seq.voice,seq.metrobeat); | 220 // flip the page |
239 | 221 if (ztime == 0){ |
240 | 222 seq.counter = (seq.counter + 1) % seq.durations.length |
241 } | |
242 | |
243 // flip the page | |
244 | |
245 if (ztime == 0){ | |
246 seq.counter = (seq.counter + 1) % seq.durations.length | |
247 socket.broadcast.emit("pageFlipfromserver", seq.voice, unit, time, seq.mm,seq.counter); | 223 socket.broadcast.emit("pageFlipfromserver", seq.voice, unit, time, seq.mm,seq.counter); |
248 //delete tockTock; | 224 //delete tockTock; |
249 step(seq); | 225 step(seq); |
250 } | 226 } |
251 | 227 |
252 // decrement the time | 228 // decrement the time |
253 ztime -= 1000 | 229 ztime -= tempoms |
254 | 230 } |
255 } | 231 |
256 | 232 var pulse = setInterval(sequenCer, tempoms); |
257 var boo = setInterval(sequenCer, tempoms); | 233 |
258 | 234 socket.on('stopSeq', function () { |
259 socket.on('stopSeq', function () { | 235 //donaldduck = mickeymouse + 7 |
260 //donaldduck = mickeymouse + 7 | 236 sequenCer.clearInterval |
261 sequenCer.clearInterval | 237 console.log("sequencer stopping...") |
262 console.log("sequencer stopping...") | 238 // grrr why wont this clearInterval work |
263 // grrr why wont this clearInterval work | 239 sequencerState = 0 |
264 sequencerState = 0 | 240 clearInterval(pulse) |
265 clearInterval(boo) | 241 stopChr(); |
266 stopChr(); | 242 }); |
267 // var countdowntick = "" | 243 }; |
268 | 244 |
269 }); | 245 //////////////// |
270 | |
271 }; | |
272 | |
273 //////////////// | |
274 | 246 |
275 socket.on('startSeq', function () { | 247 socket.on('startSeq', function () { |
276 if (sequencerState == 0) { | 248 if (sequencerState == 0) { |
277 console.log("sequencer starting...") | 249 console.log("sequencer starting...") |
278 startChr(); | 250 startChr(); |
279 step(seqA); step(seqB); step(seqC); step(seqD); | 251 step(seqA); step(seqB); step(seqC); step(seqD); |
280 ztime =-1; | 252 ztime =-1; |
281 } | 253 } |
282 else console.log("sequencer already started...") | 254 else console.log("sequencer already started...") |
283 }); | 255 }); |
284 | 256 |
285 | |
286 step = function (seq) { | 257 step = function (seq) { |
287 //clearInterval(seq.boo); | 258 //clearInterval(seq.boo); |
288 //clearInterval(countdowntick); | 259 //clearInterval(countdowntick); |
289 countdowntick(seq) | 260 countdowntick(seq) |
290 sequencerState=1; | 261 sequencerState=1; |
291 | 262 }; |
292 }; | |
293 | 263 |
294 socket.on('resetSeq', function () { | 264 socket.on('resetSeq', function () { |
295 console.log("reset") | 265 console.log("reset") |
296 resetChr(); | 266 resetChr(); |
297 }); | 267 }); |
298 | 268 |
299 | 269 |
300 | |
301 | |
302 | |
303 | |
304 //////////////////////////////////////////// | 270 //////////////////////////////////////////// |
305 // some latency calculations | 271 // some latency calculations |
306 /////////////////////////////////////////// | 272 /////////////////////////////////////////// |
307 | 273 |
308 /* | 274 /* |
309 a ping is periodically broadcast to all connected clients each | 275 a ping is periodically broadcast to all connected clients each |
310 connected returns a pong to the server via an "emit" and in turn | 276 connected returns a pong to the server via an "emit" and in turn |
311 the server returns each unique client a report of the latency | 277 the server returns each unique client a report of the latency |
312 via another emit - the emit only sends to the source of the | 278 via another emit - the emit only sends to the source of the |
313 request, whereas the broadcast.emit.. broadcasts.. ie to all | 279 request, whereas the broadcast.emit.. broadcasts.. ie to all |
314 connected clients | 280 connected clients |
315 | 281 |
316 TODO: smooth range and average out results to remove erratic ping | 282 TODO: smooth range and average out results to remove erratic ping |
317 times. | 283 times. |
318 | 284 |
319 TODO: | 285 TODO: |
320 The result then needs to be used to stagger outgoing messages to | 286 The result then needs to be used to stagger outgoing messages to |
329 // periodically broadcast a ping | 295 // periodically broadcast a ping |
330 | 296 |
331 function serverTime(freq) { | 297 function serverTime(freq) { |
332 | 298 |
333 if (pinging==0){ | 299 if (pinging==0){ |
334 st = setInterval(function() { | 300 st = setInterval(function() { |
335 var pinging=1; | 301 var pinging=1; |
336 var d = new Date(); var n = d.getTime(); | 302 var d = new Date(); var n = d.getTime(); |
337 socket.emit("timeFromServer", n); | 303 socket.emit("timeFromServer", n); |
338 //socket.broadcast.emit("timeFromServer", n); | 304 //socket.broadcast.emit("timeFromServer", n); |
339 }, 1000); } | 305 }, 1000); } |
340 else console.log("already pinging") | 306 else console.log("already pinging") |
341 } | 307 } |
342 | 308 |
343 // recieve the pong calculate the latency and | 309 // receive the pong calculate the latency and |
344 // return the response to the client | 310 // return the response to the client |
345 | 311 |
346 socket.on("clientTimeResponse", function(x) { | 312 socket.on("clientTimeResponse", function(x) { |
347 var d = new Date(); var n = d.getTime(); | 313 var d = new Date(); var n = d.getTime(); |
348 var latency = (n-x)/2; | 314 var latency = (n-x)/2; |
349 //console.log("SERVERTIME:"+x + " LATENCY:" + latency); | 315 //console.log("SERVERTIME:"+x + " LATENCY:" + latency); |
350 socket.emit("latencyFromServer", latency); | 316 socket.emit("latencyFromServer", latency); |
351 | |
352 }); | 317 }); |
353 | 318 |
354 // this is the trigger from the control client to start the process | 319 // this is the trigger from the control client to start the process |
355 // maybe remove this and have latency connections constantly running | 320 // maybe remove this and have latency connections constantly running |
321 | |
356 /* | 322 /* |
357 socket.on("getLatencies", function(x){ | 323 socket.on("getLatencies", function(x){ |
358 serverTime(x); | 324 serverTime(x); |
359 }); | 325 }); |
360 */ | 326 */ |
361 | 327 |
362 serverTime(1000); | 328 serverTime(1000); |
363 | 329 |
364 socket.on('disconnect', function(client) { | 330 socket.on('disconnect', function(client) { |
365 console.log(socket.nickname + " is gone..." ) | 331 console.log(socket.nickname + " is gone..." ) |
366 clearInterval(st); | 332 clearInterval(st); |
333 | |
334 if (!socket.nickname) return; | |
335 | |
336 delete nicknames[socket.nickname]; | |
337 socket.broadcast.emit('announcement', socket.nickname + ' disconnected'); | |
338 socket.broadcast.emit('nicknames', nicknames); | |
367 }); | 339 }); |
368 | 340 |
369 }); | 341 }); |
370 | |
371 | |
372 | |
373 | |
374 | |
375 | |
376 |