1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930 | ////////////////////////////////////////////////////////////////////////////
//
// cut to fit the toolspun course:
// Discussing Creative Code in Comments
// by Nick Montfort and Stephanie Strickland
//
// This is a discussion of (and an edition of) a work first published in
// Dear Navigator in the Winter 2010 issue:
// http://blogs.saic.edu/dearnavigator/winter2010/
//
//
// Sea and Spar Between
// by Nick Montfort and Stephanie Strickland
//
// a poetry generator which defines a space of language
// populated by a number of stanzas comparable to the number
// of fish in the sea, around 225 trillion
//
// To use Sea and Spar Between the current file (sea_spar.js) and the
// following files are required, all in the same directory:
//
// index.html The main Web page, with the interface to the generator.
// reading.html Instructions on how to read Sea and Spar Between.
// style.css The CSS stylesheet for both Web pages.
// canvastext.js a public domain file by Jim Studt containing the font.
//
// Use our version of canvastext.js; minor changes have been made to it.
//
// "cut to fit the toolspun course" includes a new gloss by the authors
// on the original JavaScript code. The code was originally published with
// some comments to assist those who might want to modify or re-use it; this
// version expands on those comments to explain more about the process of
// developing the generator and to reflect on the nature of comments and the
// glossing of code. This file, including comments both practical and
// reflective, is offered as one model for the criticism of literary works
// written in code.
//
// Comments are a very ordinary facility provided by programming languages.
// In BASIC, for instance, a comment begins with "REM." In JavaScript (as in
// several other languages) a comment can be on a line beginning with "//" or
// can be demarcated with an initial "/*" and a concluding "*/".
//
// One print precursor for comments is the 1817 version of The Rime
// of the Ancient Mariner, which, in addition to featuring sea and spar,
// includes the gloss Coleridge wrote in an explanatory but also poetic
// mode, meant to be a poet's addition to the poem that offers a new
// perspective on the original text in a different voice.
//
// The title of this version, this code-including-long-comments, is a line
// generated by the Sea and Spar Between program itself:
//
// cut to fit the toolspun course
//
// Our comments are placed between lines of the actual, working, "toolspun"
// code. Rather than write an essay, we have "cut" our glosses to fit the
// "toolspun course" of this program.
//
// As in the original release, we include a copyright notice and a license
// to assert that anyone may copy and/or modify any or all of our work
// subject to a short list of conditions. In addition to declaring this
// to be free software, we also explain that the program is offered without
// any sort of warranty.
//
// Copyright (c) 2012, Nick Montfort and Stephanie Strickland
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the names of the copyright holders nor the names of any other
// contributor may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Although our project mainly engages computation, two book-length works,
// and the small-scale collaboration of two authors, we recognize the
// potential of the network to foster different sorts of work and new,
// radical collaborations. By offering Sea and Spar Between explicitly as
// free software, we make it clear that authors and programmers can take from
// it anything they find useful, just as we reworked and remixed Moby-Dick
// with the poems of Emily Dickinson.
//
// This license, of course, is not just included here for purposes of
// commentary; it applies to this edition of the text as well.
//
// We used a static code checking tool called jslint to avoid certain types of
// programming errors. The commands to jslint are themselves expressed as
// comments -- this time directed at another computer program, rather than
// a human reader.
//
// The following two lines are used for checking the code with jslint:
/*jslint browser: true*/
/*global window unescape CanvasTextFunctions*/
// The code begins with variable declarations that define numeric and string
// data. The first declaration is for the program's internal representation
// of where the mouse pointer is. To begin with, it is set to coordinates 0,0.
//
// The integer variables mouseX and mouseY:
var mouseX = 0, mouseY = 0;
// Our program defines an immense lattice of stanzas. Each time the program
// is run, the reader is deposited at a random location. The following two
// lines of code set those (random) lattice coordinates and are the only
// code in the program that involves randomness. If someone reading the poem
// wants to arrive at a fixed location instead, such an option is provided for
// later in the code. One can enter a URL in the form of ?x,y (for example
// ?0,0 or ?12345,22680099) to draw the lattice centered at that exact
// coordinate, or one can enter coordinates in the navigation box at the
// bottom of the browser window.
//
// The integer variables baseI and baseJ are so named because they indicate
// a specific (I,J) coordinate within the lattice, not an (X,Y) point on
// the screen of the sort that mouseX and mouseY indicate:
var baseI = Math.round(Math.random() * 14992383);
var baseJ = Math.round(Math.random() * 14992383);
// The lattice is 14992383 x 14992383 units in size, on account of certain
// rhetorical choices we made (explained later), so the choice of a
// random location is done using this number. The rounding removes the
// values after the decimal point to provide an integer coordinate.
//
// Mapping structures to intention, the reader is thus deposited "at sea,"
// located in a poem which surrounds or environs him or her, affording the
// view of a sailor, yet not in Pound's sense of a sailor's view of the
// shore -- here, now, in this poem the shore has disappeared.
//
// The number of different stanzas is finite and they are not arranged
// randomly, yet the extent of them, in combination with the speed at
// which they succeed one another, is restive enough to seem "terrifying,"
// according to one accomplished digital writer and reader, or "sublime"
// according to another. By these terms they signal, we believe, an
// abundance exceeding normal, human scale combined with a dizzying
// difficulty of orientation.
//
// The next several variables deal with typography and visual aspects.
//
// The variables canvas and context are assigned values just after the
// program starts, in setup(). They are used to refer to the canvas
// element and one possible graphics context for this element, the 2D
// context:
var canvas, context;
// The integer variable fontSize:
var fontSize = 12;
// Five integer variables determine the typographical appearance of
// the stanzas. These, along with fontSize, are set every time
// changeFontSize() is called, which happens in response to several
// interface events. changeFontSize is also called just after the program
// starts, within setup():
var lineHeight, column, stanzaHeight, spacingX, spacingY;
//
// DATA -- words and short phrases that are combined by the generator --
// are defined in the next section of the code.
//
// This section contains the literary data, words and short phrases that our
// program combines in stanzaic patterns defined by us in order to recall or
// exaggerate stylistic elements of the source texts as we perceived them.
// These texts are the poems of Emily Dickinson (1830-1886) and the complete
// text of Moby-Dick (1851) by Herman Melville (1819-1891).
//
// If someone were to replace our words and phrases with new texts, a
// generator with a similar appearance and similar functioning, but with a
// new vocabulary, would be defined. That is, it is practically possible to
// create a new generator, a remix or appropriation of this one, by
// replacing only the data in this section. If this is done and the code
// is not otherwise modified, the system will assemble language in the same
// way, but it will work on different language.
//
// In this particular literary system, the words and phrases that follow
// are the data. Other literary programs use different models with different
// perspectives, but ours is laid out below.
//
// The array variable shortPhrase contains short phrases, almost all of
// which are taken from Melville's Moby-Dick:
var shortPhrase = ['circle on', 'dash on', 'let them', 'listen now', 'loop on', 'oh time', 'plunge on', 'reel on', 'roll on', 'run on', 'spool on', 'steady', 'swerve me?', 'turn on', 'wheel on', 'whirl on', 'you--too--', 'fast-fish', 'loose-fish'];
// The typical way we organized phrase and word data, in these and
// other arrays, was by alphabetizing the elements. However, in the
// shortPhrase, we chose to place "fast-fish" and "loose-fish" (Melville's
// terms for a whale held fast to a boat or loose at sea)
// next to each other at the end. This choice entails that they will appear
// fairly close to each other in the generated output.
//
// The array variable dickinsonNoun contains common nouns from Dickinson's
// poems. We judged these nouns as common using a frequency analysis of the
// words in the poems. The array has internal structure (it is an array of
// arrays), with the nouns grouped by number of syllables:
var dickinsonNoun = [
['air', 'art', 'care', 'door', 'dust', 'each', 'ear', 'earth', 'fair', 'faith', 'fear', 'friend', 'gold', 'grace', 'grass', 'grave', 'hand', 'hill', 'house', 'joy', 'keep', 'leg', 'might', 'mind', 'morn', 'name', 'need', 'noon', 'pain', 'place', 'play', 'rest', 'rose', 'show', 'sight', 'sky', 'snow', 'star', 'thought', 'tree', 'well', 'wind', 'world', 'year'],
['again', 'alone', 'better', 'beyond', 'delight', 'dying', 'easy', 'enough', 'ever', 'father', 'flower', 'further', 'himself', 'human', 'morning', 'myself', 'power', 'purple', 'single', 'spirit', 'today'],
['another', 'paradise'],
['eternity'],
['immortality']
];
// The array variable courseStart provides the three alternative line
// beginnings for the compoundCourseLine:
var courseStart = ['fix upon the ', 'cut to fit the ', 'how to withstand the '];
// The following syllables, which were commonly used as words by either
// Melville or Dickinson, are combined by the generator into compound words.
// The smallest unit of language that we model is the syllable. Since our
// stanzas are designed to be reminiscent of Dickinson's own (themselves
// eccentric) and to convey some of the flavor of Melville's style as well,
// we decided to track syllable counts. Our decision to create kennings from
// pre-selected commonly used words in Dickinson's poems and in Moby-Dick
// also required tracking syllable counts in order to fit the stanzaic line.
//
// The array variables dickinsonSyllable and melvilleSyllable:
var dickinsonSyllable = ['bard', 'bead', 'bee', 'bin', 'bliss', 'blot', 'blur', 'buzz', 'curl', 'dirt', 'disk', 'doll', 'drum', 'fern', 'film', 'folk', 'germ', 'hive', 'hood', 'husk', 'jay', 'pink', 'plot', 'spun', 'toll', 'web'];
var melvilleSyllable = ['ash', 'bag', 'buck', 'bull', 'bunk', 'cane', 'chap', 'chop', 'clam', 'cock', 'cone', 'dash', 'dock', 'edge', 'eel', 'fin', 'goat', 'hag', 'hawk', 'hook', 'hoop', 'horn', 'howl', 'iron', 'jack', 'jaw', 'kick', 'kin', 'lime', 'loon', 'lurk', 'milk', 'net', 'pike', 'rag', 'rail', 'ram', 'sack', 'salt', 'tool'];
// We wanted the code to expose which 19th century author was the source of
// a syllable -- either can be the sole contributor within a kenning, or
// the contributor of the first or second syllable. When compound words are
// actually created, they are created from a combined array. The following
// code joins the two arrays and sorts them alphabetically.
//
// The array variable syllable holds the combined, sorted syllables:
var syllable = dickinsonSyllable;
syllable.concat(melvilleSyllable);
syllable.sort();
// Dickinson's poems include many words ending in "less," such as "artless."
// Melville uses certain interesting "less" words as well (e.g.,
// "masterless"), but in Dickinson the use of the suffix seemed to us to be
// working to compress the language and to withdraw, at the end of the word,
// the promise of the word's beginning. Some of their stems (such as "art")
// follow.
//
// The array variable dickinsonLessLess, like dickinsonNoun, is an array of
// arrays with the contents grouped by number of syllables:
var dickinsonLessLess = [
['art', 'base', 'blame', 'crumb', 'cure', 'date', 'death', 'drought', 'fail', 'flesh', 'floor', 'foot', 'frame', 'fruit', 'goal', 'grasp', 'guile', 'guilt', 'hue', 'key', 'league', 'list', 'need', 'note', 'pang', 'pause', 'phrase', 'pier', 'plash', 'price', 'shame', 'shape', 'sight', 'sound', 'star', 'stem', 'stint', 'stir', 'stop', 'swerve', 'tale', 'taste', 'thread', 'worth'],
['arrest', 'blanket', 'concern', 'costume', 'cypher', 'degree', 'desire', 'dower', 'efface', 'enchant', 'escape', 'fashion', 'flavor', 'honor', 'kinsman', 'marrow', 'perceive', 'perturb', 'plummet', 'postpone', 'recall', 'record', 'reduce', 'repeal', 'report', 'retrieve', 'tenant'],
['latitude', 'retriever']
];
// The array variable dickinsonFlatLessLess is defined and set, in the
// following lines, to hold a "flattened" version of dickinsonLessLess --
// one long array of words, sorted and without the internal structure:
var dickinsonFlatLessLess = dickinsonLessLess[0];
dickinsonFlatLessLess.concat(dickinsonLessLess[1], dickinsonLessLess[2]);
dickinsonFlatLessLess.sort();
// The array variable upVerb includes verbs that can suggest a positive mood:
var upVerb = ['bask', 'chime', 'dance', 'go', 'leave', 'move', 'rise', 'sing', 'speak', 'step', 'turn', 'walk'];
// The array variables butBeginning and butEnding provide the words that
// begin and end one type of line, the butLine:
var butBeginning = ['but', 'for', 'then'];
var butEnding = ['earth', 'sea', 'sky', 'sun'];
// The array variable threeToFiveSyllable holds two-, three-, and four-
// syllable words from dickinsonNoun along with two-syllable words from
// dickinsonLessLess:
var threeToFiveSyllable = dickinsonNoun[2];
threeToFiveSyllable.concat(dickinsonNoun[3] + dickinsonNoun[4] + dickinsonLessLess[2]);
// The array variable twoSyllable holds the one-syllable words from both
// of those lists:
var twoSyllable = dickinsonNoun[1];
twoSyllable.concat(dickinsonLessLess[1]);
// The array variable nailedEnding holds words (from both authors) that
// complete one type of line, the nailedLine:
var nailedEnding = ['coffin', 'deck', 'desk', 'groove', 'mast', 'spar', 'pole', 'plank', 'rail', 'room', 'sash'];
// The counting and quantitative analysis of text that we did
// systematically was simple and straightforward, but it is worth noting
// that it was done in an exploratory mode -- to open up new literary
// questions and to identify new poetic possibilities; not, for instance,
// to determine authorship or to support any kind of statistical analysis.
// In this regard, our project bears some relation to the "distant reading"
// of Franco Moretti and to Tanya Clement's "not-reading" of Gertrude
// Stein's The Making of Americans. Because we are looking for new
// understanding or insight into two texts, not massive numbers of books,
// Sea and Spar Between has more in common with Clement's work, which in
// fact used computational analysis of the text to supplement, not to
// replace, other sorts of readings.
//
// FUNCTIONS to generate each type of line, assemble stanzas, draw the
// lattice of stanzas in the browser, and handle input and other events
// are defined in the next section of the code.
//
// If this section is modified, but the data above are left the same, the
// words and short phrases that have been defined can be combined and
// presented in new ways.
//
// Writing comments in code, like glossing a text, encourages specificity and
// reference to particular lines.
//
// Some general discussion is necessary, however. In this transition from
// "data" to "functions," it is appropriate to reflect on the mix of data
// and instructions in this program and on what the instructions actually do.
//
// Without comments, the code for Sea and Spar Between is 11558 bytes -- less
// than 12 KB and only about 1300 "words." Of this, 3295 bytes (about 28%)
// are used to specify data, to declare and set up variables. So, the program
// is not dominated by data. In contrast, a program that simply displays a
// long, static text would be mostly data (the text that was to be shown) and
// would have only a small amount of code that causes the computer to
// operate and to "print" or display that text.
//
// However, most of the code in Sea and Spar Between is used to manage the
// interface and to draw the stanzas in the browser's canvas region. Only
// 2609 bytes of the code (about 22%) are actually used to combine text
// fragments and generate lines. The remaining 5654 bytes (about 50%)
// deals with the display of the stanzas and with interactivity.
//
// We define seven template lines: three first and four second lines. These
// line templates and the consequences they involve were designed to evoke
// distinctive rhetorical gestures in the source texts, as judged
// intuitively by us, and to foreground Dickinson's strong use of negation.
//
// The following first line functions are part of the poetry generator
// proper, and are used to produce the first line of a couplet.
//
// The function shortLine() can generate, e.g., "swerve me?":
function shortLine(n)
{
return shortPhrase[n % shortPhrase.length];
// % is the mod operator. "n % m" yields the remainder from when n
// is divided by m. So, for instance, the value of "n % 10" is
// at least 0 and at most 9. In this case, % allows any value n
// to be used to pick an element of the array shortPhrase.
}
// In all the other cases, we developed first lines by selecting the
// categories of words that are used in them and defining an overall syntax.
//
// The function oneNounLine() can generate, e.g., "one wind one mind one year
// one grace":
function oneNounLine(n)
{
var a, b, c, d = n % dickinsonNoun[0].length;
n = Math.floor(n / dickinsonNoun[0].length);
c = n % dickinsonNoun[0].length;
n = Math.floor(n / dickinsonNoun[0].length);
b = n % dickinsonNoun[0].length;
n = Math.floor(n / dickinsonNoun[0].length);
a = n % dickinsonNoun[0].length;
return 'one ' + dickinsonNoun[0][a] + ' one ' + dickinsonNoun[0][b] + ' one ' + dickinsonNoun[0][c] + ' one ' + dickinsonNoun[0][d];
}
// The function compoundCourseLine() can generate, e.g., "cut to fit the
// toolspun course":
function compoundCourseLine(n)
{
var a, b, c = n % syllable.length;
n = Math.floor(n / syllable.length);
b = n % syllable.length;
n = Math.floor(n / syllable.length);
a = n % courseStart.length;
return courseStart[a] + syllable[b] + syllable[c] + ' course';
}
// The function firstLine() returns the first line of a pair, which is one of
// the three types above:
function firstLine(n)
{
var m = Math.floor(n / 4);
if (n % 4 < 2)
{
return shortLine(m);
}
if (n % 4 === 2)
{
return oneNounLine(m);
}
return compoundCourseLine(m);
}
//
// Second line functions follow.
//
// The function riseAndGoLine can generate, e.g., "graspless dance and go":
function riseAndGoLine(n)
{
var a, b, c = n % upVerb.length, dash = '';
n = Math.floor(n / upVerb.length);
b = n % upVerb.length;
n = Math.floor(n / upVerb.length);
a = n % dickinsonFlatLessLess.length;
if (dickinsonFlatLessLess[a] in dickinsonLessLess[0])
{
dash = ' --';
}
return dickinsonFlatLessLess[a] + 'less ' + upVerb[b] + ' and ' + upVerb[c] + dash;
}
// While the previous function does produce such lines, it does not work as
// first intended or as a quick reading of the code might suggest. An
// examination of the code above suggests that it will produce the line
// "graspless dance and go --" (with a dash at the end), but it does not,
// because the condition on the if statement is never true. A similar
// condition works in Python, but not in this programming language,
// JavaScript.
//
// This mistake came about because the generator was originally written in
// Python and converted to JavaScript. The program is still suitable; we
// were pleased with the output that lacked the final dash. Our mistake in
// leaving these lines in place, however, makes detailed understanding more
// difficult for those who might seek to modify and build on this code. At
// the same time, it shows that even fairly short programs can definitely
// retain traces of their making.
// The function butLine can generate, e.g., "but taleless is the earth":
function butLine(n)
{
var a, b, c = n % butEnding.length;
n = Math.floor(n / butEnding.length);
b = n % dickinsonFlatLessLess.length;
n = Math.floor(n / dickinsonFlatLessLess.length);
a = n % butBeginning.length;
return butBeginning[a] + ' ' + dickinsonFlatLessLess[b] + 'less is the ' + butEnding[c];
}
// The function exclaimLine can generate, e.g., "another! myself!":
function exclaimLine(n)
{
var a, b = n % twoSyllable.length;
n = Math.floor(n / twoSyllable.length);
a = n % threeToFiveSyllable.length;
return threeToFiveSyllable[a] + '! ' + twoSyllable[b] + '!';
}
// The function nailedLine() produces a line beginning "nailed to the ..."
// In Moby-Dick, Ahab nails a doubloon to the mast, offering it as a reward
// to the one who sees the white whale first. This line template is meant to
// semantically mirror an extended attempt to find axial support, both by the
// reader of our poem and within Melville's novel, where being "at sea"
// involves trying to locate a moral compass, trying to track down a quarry,
// trying to control the crew through bribery, and using the mast itself as
// a pointer to the stars in 19th-century navigation.
function nailedLine(n)
{
var a = n % nailedEnding.length;
return 'nailed to the ' + nailedEnding[a];
}
// The function secondLine() returns one of the four types of line just
// described to serve as the second line of a couplet:
function secondLine(n)
{
var m = Math.floor(n / 4);
if (n % 4 === 0)
{
return riseAndGoLine(m);
}
if (n % 4 === 1)
{
return butLine(m);
}
if (n % 4 === 2)
{
return exclaimLine(m);
}
return nailedLine(m);
}
//
// Functions related to drawing text and handling events follow.
//
// Here is where the poetry generation code ends and the code for display and
// interactivity begins. The following function, "drawPair," actually does
// some poetry generation -- it juxtaposes the first and second lines -- as
// it draws those lines on the screen. In counting poetry-generation code and
// display/interface code, this next function was (generously) counted with
// the poetry-generation code.
// The function drawPair() displays a couplet, two lines, on the canvas.
// The drawing of these is done by calling the drawText method (in
// canvastext.js) using the graphical coordinates x, y. The lines themselves
// are determined by the functions firstLine and secondLine (above), which
// are given the lattice coordinates i, j.
//
// Sea and Spar Between produces four-line stanzas made up of two couplets;
// each of the couplets is produced by a call to drawPair, so that the
// couplet begins in one of three ways (with a shortLine, a oneNounLine, or a
// compoundCourseLine) and ends in one of four ways (with a riseAndGoLine, a
// butLine, an exclaimLine, or a nailedLine). Each of these have their own
// combinatorial rules for assembling language given a particular point on
// the lattice. These rules are simple; there is no elaborate AI architecture
// or learned statistical process at work here.
//
// In the background of such a project lies Raymond Llull's volvelle,
// Jonathan Swift's literary machine, and, more directly, early
// computational projects such as Brion Gysin and Ian Sommerville's
// permutation poems, Alison Knowles and James Tenney's House of Dust,
// and many other historic and contemporary investigations of ways to combine
// language fragments.
//
// The lattice of Sea and Spar Between is deterministic; each point of it
// maps to a particular combination of words and lines so that (in theory)
// the system can enumerate all possible texts. In this regard the system is
// most similar to Gysin and Sommerille's "I AM THAT I AM," a poem that
// includes every permutation of that phrase:
function drawPair(i, j, x, y)
{
y += lineHeight;
context.drawText('sans', fontSize, x, y, firstLine(i + j + 1));
y += lineHeight;
context.drawText('sans', fontSize, x, y, ' ' + secondLine(Math.abs(i - j) + 1));
}
// A Web page is an imperfect but evolved and elaborate mechanism for
// displaying text. It would be possible to simply generate HTML elements and
// have this JavaScript program display text directly on a Web page in
// whatever font the browser uses.
//
// Instead, Sea and Spar Between uses the canvas element that is available in
// HTML 5 and in most modern browsers. This is a region for graphical display.
// To write text to this region, a font has to be defined (as is done in a
// separate file) and the whole system of typographical display has to be built
// up more or less from scratch. The advantage, however, is greater control
// over the visual display of the text and the ways the user can interact with
// the system.
// The function readCoords() parses the coordinates in the URL (if there are
// any) and uses those as the base lattice coordinates.
function readCoords()
{
var params = window.location.search, a;
if (params.substring(0, 1) === '?')
{
params = params.substring(1);
}
params = params.split(',');
for (a = 0; a < params.length; a += 1)
{
params[a] = unescape(params[a]);
}
return params;
}
// The function drawCoords() displays the numerical lattice coordinates of
// the central stanza directly above that stanza:
function drawCoords(i, j, x, y)
{
var stroke = context.strokeStyle;
context.strokeStyle = "rgba(255,255,255,1.2)";
context.drawText('sans', 12, x, y, i + ' : ' + j);
context.strokeStyle = stroke;
}
// The function canonical() converts an integer to a "canonical" lattice
// coordinate (a value that is not less than 0 and not more than 14992383)
// to handle negative and very large inputs. This makes the "sea" a torus,
// looping in both the right/left direction and in the up/down direction.
// The large number of possible permutations of the line of the form
// "one _ one _ one _ one _" determined the dimension, 14992384:
function canonical(value)
{
value = value % 14992384;
if (value < 0)
{
value = value + 14992384;
}
return value;
}
// The function drawLattice() is the program's main function. It draws the
// entire visible portion of the lattice in the browser window:
function drawLattice(startI, startJ)
{
var startX, startY, i, j, x, y;
// Draw the background:
context.fillStyle = "rgba(199,220,254,1)";
context.fillRect(0, 0, canvas.width, canvas.height);
startX = (canvas.width - column) / 2; // X position of central stanza.
startY = (canvas.height - stanzaHeight) / 2; // Y position.
// Draw the coordinate of that stanza:
drawCoords(canonical(baseI + startI), canonical(baseJ + startJ), startX, startY);
// At this point startX and startY indicate where the central stanza
// will be drawn. They need to be adjusted if the window is large
// enough or font small enough to accommodate other stanzas.
while (startX > 0) { // Until we are at 0 or off the page to the left,
startX -= spacingX; // step back one space ...
startI -= 1; // so we can draw the previous, (i-1)th stanza
} // to the left.
while (startY > 0) { // Until we are at 0 or off the top of the page,
startY -= spacingY; // step up one stanza ...
startJ -= 1; // so we can draw the previous stanza with
} // the pair of lines (j-2) and (j-1) up above.
i = canonical(baseI + startI);
// i now holds the correct first lattice coordinate for the upper left
// stanza.
for (x = startX; x <= canvas.width; x += spacingX)
{
j = canonical((baseJ + startJ) * 2);
// The multiplication by two is so that the lattice moves up and down
// two pairs (one stanza) at a time. If this weren't done the breaks
// between stanzas would not be maintained.
for (y = startY; y <= canvas.height; y += spacingY - lineHeight * 3)
{
// A stanza is drawn by drawing one pair of lines, then another.
drawPair(i, j, x, y, lineHeight);
j = canonical(j + 1);
y += lineHeight * 3;
drawPair(i, j, x, y, lineHeight);
j = canonical(j + 1);
}
i = canonical(i + 1);
}
}
// The function changeFontSize() works by adding the value of delta to the
// current font size, determining the new spacing, and redrawing the
// lattice:
function changeFontSize(delta)
{
fontSize += delta;
fontSize = Math.max(4, fontSize);
lineHeight = context.fontAscent('sans', fontSize) + context.fontDescent('sans', fontSize);
column = fontSize * 22;
stanzaHeight = lineHeight * 5;
spacingX = fontSize * 38;
spacingY = stanzaHeight * 2;
drawLattice(parseInt(mouseX / 3, 10), parseInt(mouseY / 3, 10));
}
// The function updateWheel() makes its changes if the mouse wheel has
// moved. This is to set up the first of several event listeners, which
// are triggered when something about the interface is changed by the user:
function updateWheel(e)
{
var evt, wheel;
evt = window.event || e; // Select available event object.
wheel = evt.detail ? evt.detail * (-120) : evt.wheelDelta;
if (wheel > 0)
{
changeFontSize(1);
}
else
{
changeFontSize(-1);
}
}
// The function markStanza() places coordinates of the central stanza in the
// navigation box:
function markStanza()
{
var textInput = document.getElementById("coords");
textInput.value = canonical(baseI + parseInt(mouseX / 3, 10)) + ",";
textInput.value += canonical(baseJ + parseInt(mouseY / 3, 10));
}
// The function keyDown() handles key presses: a, A, z, Z, SPACE, and the
// arrow keys. a and z zoom in and out, the spacebar is used to mark a
// stanza, and the arrow keys are used to move a "screenful" in any
// direction.
//
// Simply identifying these keys is a rather elaborate process; a different
// method is used for a, A, z, Z, and the space that is used to identify the
// arrow keys. Although the Web allows for widespread access to a program like
// this, different browsers function differently, and care is needed even to
// identify what keys are being pressed in a way that works across browsers:
function keyDown(e)
{
var key = String.fromCharCode(e.keyCode);
if (key === "a" || key === "A")
{
changeFontSize(1);
}
else if (key === "z" || key === "Z")
{
changeFontSize(-1);
}
else if (key === ' ')
{
markStanza();
}
// The rest of these if statements handle the arrow keys.
else if (e.keyCode === 37)
{
baseI = canonical(baseI - 1);
drawLattice(parseInt(mouseX / 3, 10), parseInt(mouseY / 3, 10));
}
else if (e.keyCode === 38)
{
baseJ = canonical(baseJ - 1);
drawLattice(parseInt(mouseX / 3, 10), parseInt(mouseY / 3, 10));
}
else if (e.keyCode === 39)
{
baseI = canonical(baseI + 1);
drawLattice(parseInt(mouseX / 3, 10), parseInt(mouseY / 3, 10));
}
else if (e.keyCode === 40)
{
baseJ = canonical(baseJ + 1);
drawLattice(parseInt(mouseX / 3, 10), parseInt(mouseY / 3, 10));
}
}
// The function mouseMove() handles the translation of the mouse.
// A different part of the lattice is drawn each time the mouse's movement
// corresponds to more than three pixels in any direction. This makes for a
// trembling, rapidly updating image of sea, or sky, or canvas. The usual
// function of the mouse, to guide a pointer around a window or screen, is
// changed so that the mouse's movement replaces texts in the lattice of
// language, surprisingly and restlessly:
function mouseMove(e)
{
mouseX = e.clientX;
mouseY = e.clientY;
drawLattice(parseInt(mouseX / 3, 10), parseInt(mouseY / 3, 10));
return false;
}
// The function mouseClick() shifts to a new region if the click is near an
// edge; it redraws the lattice in any case. To explore the immense lattice,
// we allow the reader to take mousing (baby) steps which investigate the
// viewer's near neighborhood -- done in mouseMove, above; giant (clicking)
// steps at screen edges which take the reader farther away from his or her
// present location (done in mouseClick, below); or precise (navigation box
// or URL input) steps, which permit the reader to arrive at any desired
// location:
function mouseClick(e)
{
if (mouseX > canvas.width * 2 / 3)
{
baseI += parseInt(canvas.width / 3, 10);
}
else if (mouseX < canvas.width / 3)
{
baseI -= parseInt(canvas.width / 3, 10);
}
if (mouseY > canvas.height * 2 / 3)
{
baseJ += parseInt(canvas.height / 3, 10);
}
else if (mouseY < canvas.height / 3)
{
baseJ -= parseInt(canvas.height / 3, 10);
}
drawLattice(parseInt(mouseX / 3, 10), parseInt(mouseY / 3, 10));
return false;
}
// The function resizeCanvas() causes the canvas element to be resized when
// the browser window is resized (for instance, when the user maximizes the
// browser window):
function resizeCanvas(e)
{
var div = document.getElementsByTagName('div')[0];
canvas.width = div.scrollWidth;
canvas.height = div.scrollHeight;
context.strokeStyle = "rgba(0,0,128,0.75)";
drawLattice(0, 0);
}
// The function setBase() sets the base lattice coordinates if it is given
// a valid array with coordinates in it. Otherwise, the existing baseI and
// baseJ values remain.
function setBase(coords)
{
var newI, newJ;
newI = parseInt(coords[0], 10);
newJ = parseInt(coords[1], 10);
if (!isNaN(newI) && !isNaN(newJ))
{
baseI = newI;
baseJ = newJ;
}
}
// The function setup() runs when the page is loaded. It initializes the
// canvas and event listeners and does other tasks that need to be done
// once, at startup:
function setup()
{
var div, newI, newJ, mouseWheelEvent, params = readCoords();
// If there were two coordinates in the URL found by readCoords(),
// use these to set the current location in the lattice:
if (params.length === 2)
{
setBase(params);
}
canvas = document.getElementsByTagName('canvas')[0];
if (!canvas.getContext)
{
return;
}
// Add event listeners for mouse movement, mouse click, key down:
canvas.onmousemove = mouseMove;
canvas.onclick = mouseClick;
document.onkeydown = keyDown;
mouseWheelEvent = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel";
if (document.attachEvent) // For IE (and Opera depending on user setting).
{
document.attachEvent("on" + mouseWheelEvent, updateWheel);
}
else if (document.addEventListener) // For WC3 browsers.
{
document.addEventListener(mouseWheelEvent, updateWheel, false);
}
// Add the text functions to the context:
context = canvas.getContext('2d');
CanvasTextFunctions.enable(context);
changeFontSize(0);
window.onresize = resizeCanvas;
resizeCanvas(null);
markStanza();
}
// The function go() is called when "enter" is pressed with focus in the
// navigation box. It updates the coordinates.
function go()
{
var textInput, coordPair, URL;
textInput = document.getElementById("coords");
coordPair = textInput.value;
coordPair = coordPair.split(' ').join('');
coordPair = coordPair.split(':').join(',');
setBase(coordPair.split(','));
drawLattice(0, 0);
}
// It is clear that works of electronic literature and digital art need to
// be studied by operating them, examining not only their outputs but also
// their interfaces. By writing about Sea and Spar Between within its main
// code file, we mean to invite critics to also look beneath the interface
// and consider the code level. Considering code allows those interested
// in aesthetic and poetic computing to learn more about the literary and
// technical decisions that were made with regard to appearance, interface,
// and underlying function.
//
// While we think that many types of poetic, aesthetic, and humanistic
// code deserve consideration, we also want to present our work in Sea
// and Spar Between as something that is related to, but distinct from, a
// typical digital humanities project. We are working to develop a
// computational poetics. In creating Sea and Spar Between, we were
// more concerned with poesis, with making, than with the analysis of
// texts. In this edition, "cut to fit the toolspun course," we have
// extended the project to show how critical discourse can be added at the
// code level. In this particular case, it is a gloss by the authors; but
// in the future, comments-as-commentary might also be written by critics,
// editors, and curators.
//
// In closing, our final claim: the most useful critique is a new
// constitution of elements. On one level, a reconfiguration of a source
// code file to add comments -- by the original creator or by a critic --
// accomplishes this task. But in another, and likely more novel, way,
// computational poetics and the code developed out of its practice
// produce a widely distributed new constitution.
|