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.