Showing preview only (3,516K chars total). Download the full file or copy to clipboard to get everything.
Repository: brunchboy/afterglow
Branch: main
Commit: bb5dfd1042f7
Files: 169
Total size: 223.1 MB
Directory structure:
gitextract_k9q7365g/
├── .gitattributes
├── .github/
│ ├── scripts/
│ │ ├── build.sh
│ │ └── build_guide.sh
│ └── workflows/
│ └── uberjar.yml
├── .gitignore
├── .htmltest.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── doc/
│ ├── README.md
│ ├── antora.yml
│ ├── ds.yml
│ ├── embedded.yml
│ ├── github-actions.yml
│ ├── modules/
│ │ └── ROOT/
│ │ ├── assets/
│ │ │ └── source/
│ │ │ ├── Afterglow logo.ai
│ │ │ ├── Blade.psd
│ │ │ ├── Clojure Beam.psd
│ │ │ ├── ColorPalette.psd
│ │ │ ├── ColorPalette2.psd
│ │ │ ├── F3.psd
│ │ │ ├── Launchpad Mini.psd
│ │ │ ├── Novation.psd
│ │ │ ├── OLA Logo.ai
│ │ │ ├── Push-2-Stopped.psd
│ │ │ ├── Push2NoEffects.psd
│ │ │ ├── PushNoEffects.psd
│ │ │ ├── Show Space.psd
│ │ │ └── WeatherSystem.psd
│ │ ├── nav.adoc
│ │ └── pages/
│ │ ├── README.adoc
│ │ ├── color.adoc
│ │ ├── cues.adoc
│ │ ├── effects.adoc
│ │ ├── fixture_definitions.adoc
│ │ ├── launchpad.adoc
│ │ ├── mapping_sync.adoc
│ │ ├── metronomes.adoc
│ │ ├── oscillators.adoc
│ │ ├── parameters.adoc
│ │ ├── push.adoc
│ │ ├── push2.adoc
│ │ ├── rendering_loop.adoc
│ │ ├── show_space.adoc
│ │ └── videos.adoc
│ └── primes.md
├── logs/
│ └── README.txt
├── package.json
├── project.clj
├── resources/
│ ├── afterglow/
│ │ └── readme.txt
│ ├── docs/
│ │ └── docs.md
│ ├── public/
│ │ ├── css/
│ │ │ ├── bootstrap-cyborg.css
│ │ │ ├── bootstrap-slider.css
│ │ │ ├── bootstrap-switch.css
│ │ │ ├── bootstrap-theme.css
│ │ │ ├── bootstrap.css
│ │ │ ├── jquery.minicolors.css
│ │ │ ├── screen.css
│ │ │ ├── show.css
│ │ │ └── web-repl.css
│ │ ├── epl-v10.html
│ │ ├── font-awesome-4.5.0/
│ │ │ ├── HELP-US-OUT.txt
│ │ │ ├── css/
│ │ │ │ └── font-awesome.css
│ │ │ ├── fonts/
│ │ │ │ └── FontAwesome.otf
│ │ │ ├── less/
│ │ │ │ ├── animated.less
│ │ │ │ ├── bordered-pulled.less
│ │ │ │ ├── core.less
│ │ │ │ ├── fixed-width.less
│ │ │ │ ├── font-awesome.less
│ │ │ │ ├── icons.less
│ │ │ │ ├── larger.less
│ │ │ │ ├── list.less
│ │ │ │ ├── mixins.less
│ │ │ │ ├── path.less
│ │ │ │ ├── rotated-flipped.less
│ │ │ │ ├── stacked.less
│ │ │ │ └── variables.less
│ │ │ └── scss/
│ │ │ ├── _animated.scss
│ │ │ ├── _bordered-pulled.scss
│ │ │ ├── _core.scss
│ │ │ ├── _fixed-width.scss
│ │ │ ├── _icons.scss
│ │ │ ├── _larger.scss
│ │ │ ├── _list.scss
│ │ │ ├── _mixins.scss
│ │ │ ├── _path.scss
│ │ │ ├── _rotated-flipped.scss
│ │ │ ├── _stacked.scss
│ │ │ ├── _variables.scss
│ │ │ └── font-awesome.scss
│ │ ├── fonts/
│ │ │ ├── Lekton/
│ │ │ │ └── SIL Open Font License.txt
│ │ │ ├── Open_Sans_Condensed/
│ │ │ │ └── LICENSE.txt
│ │ │ └── Roboto/
│ │ │ └── LICENSE.txt
│ │ ├── js/
│ │ │ ├── BootstrapMenu.js
│ │ │ ├── bootstrap.js
│ │ │ ├── jquery-2.1.4.js
│ │ │ ├── jquery.console.js
│ │ │ ├── jquery.websocket-0.0.1.js
│ │ │ ├── npm.js
│ │ │ ├── show_updates.js
│ │ │ └── web-repl.js
│ │ └── shaders/
│ │ └── vertex.glsl
│ └── templates/
│ ├── about.html
│ ├── base.html
│ ├── console.html
│ ├── cue_grid.html
│ ├── current-scene-fragment.js
│ ├── current-scene.json
│ ├── error.html
│ ├── fixture-definition.clj.template
│ ├── fragment.glsl
│ ├── home.html
│ ├── link_menu.html
│ ├── show.html
│ ├── sync_menu.html
│ └── visualizer.html
├── src/
│ └── afterglow/
│ ├── beyond.clj
│ ├── carabiner.clj
│ ├── channels.clj
│ ├── controllers/
│ │ ├── ableton_push.clj
│ │ ├── ableton_push_2.clj
│ │ ├── color.clj
│ │ ├── launchpad_mini.clj
│ │ ├── launchpad_mk2.clj
│ │ ├── launchpad_pro.clj
│ │ └── tempo.clj
│ ├── controllers.clj
│ ├── core.clj
│ ├── coremidi4j.clj
│ ├── dj_link.clj
│ ├── effects/
│ │ ├── channel.clj
│ │ ├── color.clj
│ │ ├── cues.clj
│ │ ├── dimmer.clj
│ │ ├── fun.clj
│ │ ├── movement.clj
│ │ ├── oscillators.clj
│ │ ├── params.clj
│ │ └── show_variable.clj
│ ├── effects.clj
│ ├── examples.clj
│ ├── fixtures/
│ │ ├── american_dj.clj
│ │ ├── blizzard.clj
│ │ ├── chauvet.clj
│ │ └── qxf.clj
│ ├── fixtures.clj
│ ├── init.clj
│ ├── midi.clj
│ ├── rhythm.clj
│ ├── show.clj
│ ├── show_context.clj
│ ├── shows/
│ │ ├── chris.clj
│ │ ├── sallie.clj
│ │ └── wedding.clj
│ ├── transform.clj
│ ├── util.clj
│ ├── version.clj
│ └── web/
│ ├── handler.clj
│ ├── layout.clj
│ ├── middleware.clj
│ ├── routes/
│ │ ├── home.clj
│ │ ├── show_control.clj
│ │ ├── visualizer.clj
│ │ └── web_repl.clj
│ └── session.clj
└── test/
└── afterglow/
├── core_test.clj
├── effects/
│ └── color_test.clj
└── effects_test.clj
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
api-doc/* -diff linguist-vendored
================================================
FILE: .github/scripts/build.sh
================================================
#!/bin/bash
# This script is run by GitHub Actions to build the cross-platform uberjar.
# If this is a full release, tweak the project to generate the correct API doc source links.
if [[ $release_snapshot == "false" ]]
then
# Update codox source link
prefix="\/github.com\/Deep-Symmetry\/afterglow\/blob\/"
sourceswap="s/${prefix}main/${prefix}${git_version}/g"
mv project.clj project.clj.old
sed "${sourceswap}" project.clj.old > project.clj
rm -f project.clj.old
fi
# Make sure Antora is installed
npm install
# Now that the project has been tweaked if needed, do the actual build
lein uberjar
# Rename the output jar to where we want it.
mv target/afterglow.jar "./$uberjar_name"
================================================
FILE: .github/scripts/build_guide.sh
================================================
#!/usr/bin/env bash
# This script is run by GitHub Actions to build the
# Antora site hosting the developer guide.
set -e # Exit if any command fails.
# There is no point in doing this if we lack the SSH key to publish the guide.
if [ "$GUIDE_SSH_KEY" != "" ]; then
# Set up node dependencies; probably redundant thanks to main workflow, but just in case...
npm install
# Build the cloud version of the documentation site. Note that the API docs are already
# built in, from the stage of building the uberjar.
npm run hosted-docs
# Make sure there are no broken links in the versions we care about.
curl https://htmltest.wjdp.uk | bash
bin/htmltest
# Publish the user guide to the right place on the Deep Symmetry web server.
rsync -avz doc/build/site/ guides@deepsymmetry.org:/var/www/guides/afterglow/
else
echo "No SSH key present, not building user guide."
fi
================================================
FILE: .github/workflows/uberjar.yml
================================================
name: Create überjar
on:
push:
branches:
- main
env:
initial_description: |
:construction: This is pre-release code for people who want to help test [what is going into the next release](https://github.com/Deep-Symmetry/afterglow/blob/master/CHANGELOG.md).
> Don’t download this if you aren’t comfortable testing code while it is under active development! Instead, look at the [latest release](https:///github.com/Deep-Symmetry/afterglow/releases/latest).
:calendar: **Ignore the “release date” above!** Because this is a snapshot release, the executables below (you may need to click to expand the Assets) will change frequently—whenever new code is pushed to the project—so you will want to _download the latest version every time you work with one_. The date you see _on each asset row_ shows you when it was last updated. Source code archive assets are not useful for snapshot releases, they will always reflect the state of the project when the snapshots began.
jobs:
build_uberjar:
name: Build cross-platform überjar
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- name: Check out main branch
uses: actions/checkout@v3
- name: Install SSH Key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.GUIDE_SSH_KEY }}
known_hosts: 'deepsymmetry.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINfnL8k99MCOHLciHb7czxFCCvF3lbmY2ase2VhdvCTN'
- name: Prepare Java
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'corretto'
- name: Install clojure tools
uses: DeLaGuardo/setup-clojure@13.0
with:
lein: 2.11.2
- name: Determine version being built
uses: Deep-Symmetry/github-version-action@v1
with:
tag-var-name: release_tag
- name: Determine überjar name from git version, and snapshot status
run: |
echo "uberjar_name=afterglow-$git_version.jar" >> $GITHUB_ENV
if [[ $release_tag =~ .*-SNAPSHOT ]]
then
echo "release_snapshot=true" >> $GITHUB_ENV
else
echo "release_snapshot=false" >> $GITHUB_ENV
fi
- name: Cache Leiningen dependencies
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-lein-${{ hashFiles('**/project.clj') }}
restore-keys: |
${{ runner.os }}-lein-
- name: Install dependencies, forcing updates of snapshots
run: lein -U deps
- name: Install antora for building user guide
run: npm install
- name: Build überjar
run: bash .github/scripts/build.sh
- name: Upload überjar
if: success()
uses: Xotl/cool-github-releases@v1
with:
mode: update
tag_name: ${{ env.release_tag }}
isPrerelease: ${{ env.release_snapshot }}
replace_assets: ${{ env.release_snapshot }}
assets: ${{ env.uberjar_name }}
github_token: ${{ github.token }}
initial_mrkdwn: ${{ env.initial_description }}
- name: Cache htmltest results
uses: actions/cache@v3
with:
path: tmp/.htmltest
key: ${{ runner.os }}-htmltest
- name: Build and publish user guide
env:
GUIDE_SSH_KEY: ${{ secrets.GUIDE_SSH_KEY }}
run: bash .github/scripts/build_guide.sh
================================================
FILE: .gitignore
================================================
*.class
*.jar
.hg/
.hgignore
/.cider_history
/.dir-locals.el
/.env
/.lein-*
/.nrepl-port
/checkouts
/classes
/doc/build
/gh-pages
/node_modules
/pom.xml
/pom.xml.asc
/research
/target
/resources/afterglow/version.edn
logs/*.log*
.idea/
*.iml
*.ipr
*.iws
# Local Netlify folder
.netlify
================================================
FILE: .htmltest.yml
================================================
DirectoryPath: "doc/build/site"
IgnoreURLs:
- "github.com/Deep-Symmetry/afterglow/blob/"
- "support.native-instruments.com/hc/"
- "help.ableton.com/hc/"
- "^http://localhost:9090"
================================================
FILE: CHANGELOG.md
================================================
# Change Log
All notable changes to this project will be documented in this file.
This change log follows the conventions of
[keepachangelog.com](http://keepachangelog.com/).
## [Unreleased][unreleased]
### Fixed
- Afterglow would crash when told to convert a QLC+ fixture definition
in the current working directory (with no path component),
[#72](https://github.com/Deep-Symmetry/afterglow/issues/72).
- External links were not being checked in the developer guide, but
even so the link checker plugin ended up failing. Switched to
htmltest, which is used on other Deep Symmetry projects, enabled
external link checking (although not yet for GitHub source links
because they are hitting rate limits, there are so many of them),
and fixed a great many broken/stale links.
### Added
- A new effect builder, `wrap-fade-in-out`, to make it easy to build
effects that fade in when you start them, and out when you tell them
to end.
- When resolving dynamic parameters, resolution will be repeated if
the result was still a dynamic parameter. This allows, for example,
oscillated parameters to be stored in show variable parameters and
swapped out during the lifetime of a cue. Resolving the variable
parameter will first obtain the oscillated parameter, which will be
resolved again for the appropriate number based on the show
metronome snapshot.
- The `variable-effect` function now takes optional keyword arguments
which allow a more meaningful effect name to be specified, and can
suppress the resolution of dynamic parameters, allowing the raw
parameter itself to be set into a show variable while the effect is
active, so that it can be used by other effects.
## [0.2.5] - 2022-01-12
### Fixed
- The built-in converter for QLC+ fixture definitions (`.qxf` files)
now supports the new XML schema used by QLC+.
- There were some off-by-one errors in the metronome, which canceled
each other out in the places they were used, but would have caused
problems in new situations. They were discovered while porting the
logic to Java for the
[electro](https://github.com/Deep-Symmetry/electro) library.
- Abrupt tempo changes could cause the metronome to jump to the wrong
place, even the wrong beat. This is now prevented, leaving you at
the exact same place in the beat grid before and after any tempo
change.
- The metronome better handles negative time, and has some improved
documentation.
### Added
- The detailed documentation has been reorganized as a Developer Guide
and is now built by [Antora](http://antora.org) to provide easier
navigation and a more readable presentation.
- The Developer Guide is now available through the built-in web server
even if you do not have a connection to the internet.
### Changed
- Upgraded to version 2.0 of the Eclipse Publice License.
## [0.2.4] - 2017-04-18
### Fixed
- The pixels of the Blizzard Pixellicious were flipped over the X
axis.
- The web interface needed to have the Beat Link `DeviceFinder`
started before trying to render the UI, or it would only partially
render because of an exception when trying to use it.
- Calling `show/patch-fixture!` returned a huge recursive data
structure which would cause the REPL to choke trying to print it,
especially in an editor. Now it just returns the key identifying the
fixture that was patched.
### Added
- A new command-line argument, `-n`, tells afterglow not to try to
launch a web browser when it is invoked from the command-line. This
is to enable headless operation on small servers which lack a
windowing environment.
## [0.2.3] - 2016-06-09
### Fixed
- Rich controller bindings (Ableton Push and Novation Launchpad
families) were not being successfully auto-bound under Windows
because of significant differences in the way their port names were
assigned on that platform. Fixing that was surprisingly tricky, but
it has been done. I look forward to someone being able to help test
this under Linux now.
- The graphical display on the Ableton Push 2 could not be connected
under Windows due to an issue in the Wayang library. That library
has been fixed, and the new version is embedded in this release.
## [0.2.2] - 2016-05-30
### Added
- The CoreMIDI4J library is now embedded within Afterglow, so it does
not need to be separately installed by the user for MIDI to work
properly on Mac OS X. If you have a separate installation of
CoreMIDI4J in `/Library/Java/Extensions`, you should remove it to
avoid version conflicts with newer versions shipped with Afterglow.
- Afterglow now uses the new beat-link library to synchronize with
Pioneer DJ Link equipment, which enables several new features such
as tracking the master player, synching to bars as well as beats,
and having the metronome reflect the current track position.
- Holding down Shift while pressing a scroll arrow on the Launchpad
family of controllers now moves you as far as possible in that
direction, as it did on the Push 2.
- Head information is now available to channel effects (which include
dimmer effects), so they can use dynamic parameters with spatial
components, just like all the other kinds of effects can.
### Fixed
- When updating large sections of the cue grid colors, we no longer
send all of them at once, because this was overflowing buffers on
the Push 2 and losing some updates. Instead we send them in batches,
ending each batch with a query, and wait for the response so we know
the controller has caught up.
- Updated to the latest release of the Wayang library for drawing on
the Push 2 display, which solves an issue that prevented the display
from working under Window.
### Changed
- The hue and saturation gauges on the Push 2 are now drawn using a
masking image so they can be anti-aliased to the same outline shape
as the other gauges, and look much cleaner.
- Boolean gauges on the Push 2 are drawn in red or green for No and
Yes values, and the transition between values is animated, to make
it easier to see what is happening, and to relate it to the encoder
rotation.
- Assigner target IDs can now have arbitrary structure appropriate to
the needs of the assigner implementation, rather than being forced
into keywords as they used to be. For the most part, they are
integers (head IDs), some are tuples (universe ID and channel pairs
for channel assigners, head ID and function keywords for head
function assigners, other things for extensions like Beyond laser
show assigners).
## [0.2.1] - 2016-04-03
### Added
- The web and Push interfaces now offer a way to save adjusted cue
variable values, so the next time the cue is launched the saved
values are used.
- Show operators can also create "Macros" by selecting a group of
running cues and choosing an unused cue grid cell. This will create
a new compound cue in the cell which will re-run all of the cues
they specified, with the same parameters they had when the macro was
created, whenever it is run. The compound cue will end all of its
component cues when you end it, and will end itself if they end
independently.
- You can now right-click on cues in the web interface to bring up a
menu of actions. The menu so far offers just the ability to delete
the cue if there is one there (this can clean up macros you no
longer want, for example).
- The Ableton Push 2 is now supported as a rich grid control
interface, taking full advantage of its color graphic display.
- Support for other members of the Novation Launchpad family of grid
controllers has been implemented:
- Launchpad Mini
- Launchpad S (untested, but the Mini, which works, is based on the S)
- Launchpad Mk2
- The identity of grid controllers is verified before binding to them,
by sending a MIDI Device Inquiry message and inspecting the
response.
- The auto-bind mechanism has been improved so much that the sample
show can now simply turn it on to fully automate the process of
detecting and binding to any compatible grid controllers that appear
in the MIDI environment, with no user or configuration effort.
- Direction and aim parameters can now be transformed by a dynamic
Java3D `Transform3D` parameter to create kaleidoscopic looks with
groups of lights.
- A new `confetti` effect which assigns random colors (and optionally
aim points) to groups of lights at intervals.
- A new `pinstripes` effect which can alternate stripes of color
across fixtures.
- Incoming MIDI System Exclusive messages can now be received and
delivered to handlers.
- Cue variables can now be Booleans, to support cues which want to be
able to adjust the direction of a sawtooth oscillator while running.
- The dimmer oscillator cues created in the sample show now include
Min and Max variables so the range over which the dimmer oscillates
can be adjusted.
- The Ableton Push mapping now lets you scroll through all variables
assigned to a cue so you can see and adjust more than the first two.
- You can now use the touch strip on the Ableton Push to immediately
jump to any part of the legal value range when adjusting a numeric,
boolean, or color cue variable, BPM, or the Dimmer Grand Master. The
LEDs on the touch strip also reflect the current value of the
variable being adjusted.
- You can use the keyboard arrow keys to navigate around the cue grid
when using the web UI, as long as no input element is focused.
- You can use the space bar to tap tempo when using the web UI, as
long as no input element is focused.
- Cues can have visualizer creation functions assigned to them, so
they can provide animated visualizer displays on the Push 2.
### Fixed
- Fixtures which had no channels assigned to the fixture itself, but
only to heads (like Blizzard's Pixellicious pixel grids) could not
be patched properly to shows, because the code checking for address
conflicts was not able to figure out the universe assigned to them.
- Chases containing only scenes would end instantly rather than
running, because of some assumptions they were making about how the
effect protocol was implemented, which scenes violated. Chases are
now more robust.
- The low-level tempo tap handler was already more useful than I was
giving it credit for, suitable for both aligning to the current beat
as well as adjusting the tempo if you hit it three or more times, so
the shift key can be used to adjust the down beat even on
unsynchronized shows. This makes it much easier to keep the lights
in sync manually!
- The color wheel is only applied when a color has sufficient
saturation for it to make sense. The threshold can be adjusted by
setting the show variable `:color-wheel-min-saturation`.
- Floating-point cue variables now stay rounded to the specified
resolution when adjusting them on the Push.
- Effects with Unicode characters in them were crashing the Ableton
Push display code, since it only handles single byte ASCII plus a
handful of special symbols used for drawing interface elements. Now
unprintable characters are substituted with an ellipsis symbol
rather than crashing.
- The entire Push display was being redrawn on each frame of
user-interface updates, and all text-labeled button states were
being set, even if they had not changed from the previous frame.
These redundant messages are no longer sent, and MIDI messages are
sent to the Push only when text and button states actually need to
change.
- The slider tooltips for cue variables in the web UI were getting in
the way of adjusting the sliders because they would appear when the
mouse was over the tooltip, not just the slider track. They could
also not be seen on mobile devices. So they have been turned off
entirely in favor of always-visible value labels.
- The documentation link in the web interface now takes you to the
proper version-specific tag of the documentation if it is a release
build. Snapshot builds take you to `master`.
- The nav bar in the show control web page is now compressed to better
fit mobile devices, since it can be used on the iPad Pro.
- Extraneous errors were being logged in the browser console because
we were sometimes returning spurious error responses for cue
variable updates, saves, and clears.
- The end effect buttons and cue variable scroll buttons under
the text area on the Ableton Push were not affecting the proper
effects when the effect view was scrolled back from the most recent.
- The effect overflow indicators in the Push text area were not
properly disappearing when enough effects ended to render them no
longer needed until the user pressed Shift to try to scroll.
- Under some circumstances the Push mapping could crash when there was
no cue associated with an effect.
- All MIDI event handler functions are now called in a context which
properly recovers from exceptions at the level of that individual
handler, so other handlers will not be affected.
- Everywhere that Afterglow was checking whether an argument was
callable as a function has been fixed to use the `ifn?` predicate
rather than `fn?` since the latter is too restrictive, and only
returns `true` for functions explicitly created using `(fn ...)`.
That precluded, for example, the idiomatic Clojure approach of using
`:x` as a function to extract the _x_ coordinate of a head when
defining a spatial parameter.
### Changed
- You no longer need to specify what kind of grid controller you are
trying to bind to in advance; the controller manager in
`afterglow.controllers` can recognize the supported controllers from
their responses to the MIDI Device Inquiry message, and instantiate
the appropriate binding. New controller implementations can register
themselves when their namespaces are loaded so the controller
manager will dispatch to them as needed.
- The code to gracefully shut down active controller bindings, which
was becoming duplicated with every new controller mapping created,
has been pulled up into the shared controllers namespace.
- The code to watch for and automatically bind to a controller when it
appears in the MIDI environment has similarly been generalized and
pulled into the shared controllers namespace.
- The ability to register an interest in all events from a specific
MIDI device was added, and the controller mapping implementations
were updated to take advantage of this, so they no longer need to
receive and filter out all the events from other devices.
- The sample show is becoming a much more practical example of how to
layer flexible color and dimmer cues, with good cue variables to add
even more dimensions.
- A lot of repetitive code in the examples namespace was consolidated
using helper functions.
- The `controllers/IOverlay` protocol was expanded to include the
ability for an overlay to handle and absorb pitch-bend messages, in
preparation for supporting the touch strip on the Ableton Push.
- The floating-point format for cue variables was changed from `float`
to `double` since that is what Clojure actually natively uses.
- All other `float` values which were created throughout Afterglow
were changed to `double` values, since that is what Clojure actually
natively uses, and they were getting promoted when used anyway.
## [0.2.0] - 2016-02-02
### Added
- Running effects are now listed on the Show Control page of the Web
UI, in descending priority order, with buttons to end them, and
controls to adjust cue parameters, including colors.
- The show's dimmer grand master is now visible and controllable
below the cue grid in the web UI.
- A rich grid controller mapping for the
[Novation Launchpad Pro](https://us.novationmusic.com/launch/launchpad-pro#).
- A rich color picker interface on the Ableton Push for cues with
color variables.
- You can now map sliders and encoders on any MIDI controller to
adjust components of a color stored in a show variable (that is,
adjust its red, green, and blue values, or its hue, saturation, and
lightness).
- The example global color and strobe cues have been upgraded to take
advantage of the new color cue parameters, so their colors can be
adjusted on the fly using the web or Push interfaces.
- The Shift button can be used with Tap Tempo buttons on controllers
and the web UI to set the start of a beat, bar, or phrase, depending
on the synchronization level of the metronome.
- Buttons or pads on any MIDI controller can now easily be mapped to
act like the smart Tap Tempo and Shift buttons on the Ableton Push
and Novation Launchpad.
- Cues can have animated colors on grid controllers including the web
interface, to help remind operators about the effects they launch,
or reflect the current value of a color variable on which they are
based.
- When binding a generic MIDI controller to launch a cue, if that
controller does not have velocity-sensitive pads, you can assign an
explicit velocity to use in the binding, for the purpose of setting
any velocity-sensitive cue variables.
- An explanation of how to bind a MIDI controller to a dimmer master
has been added to the mapping documentation.
- Animated GIFs in the documentation illustrate how the cue user
interface works.
### Fixed
- Using dynamic color variables to as inputs for other dynamic color
parameters could cause crashes when trying to adjust the incoming
color values due to a longstanding subtle bug, which has been fixed.
- Ordinary MIDI control surfaces were sometimes being mistakenly
identified as candidate sources of Tratktor beat phase information,
leading to exceptions in the log file. Now only devices sending
clock pulses are considered beat phase candidates.
- Cue colors were improved in the web interface and on the Push to
make it easier to see which cues are active, as well as to make the
colors more faithful to the cue's intent.
- The text labels on cues in the web interface are now more legible
because they use a contrasting color based on the cell's perceived
brightness.
- Incompatible cues are now identified not just from matching effect
keys, but also from their `:end-keys` lists, so the web and
controller interfaces provide even more guidance.
- Launching cues from the web interface was not setting any value for
cue variables configured to be velocity sensitive, which was
sometimes causing issues. Now the assignment of velocity-adjustable
variables happens in the process of launching any cue from the grid,
and a default velocity of 127 is assumed if none is specified.
- The entire frame of a user interface being rendered on a grid
controller or the web interface now uses the same metronome
snapshot, to represent a consistent point in time.
- Some testing in a Windows virtual machine revealed issues when
working with standard Java Midi implementations (as opposed to
CoreMidi4J on the Mac). These were addressed.
### Changed
- Updated to newly-released Clojure 1.8 for improved performance.
- Deprecated functions were removed:
- `afterglow.effects.color/find-rgb-heads` (instead use `afterglow.channels/find-rgb-heads`)
- `afterglow.effects.color/has-rgb-heads?` (instead use `afterglow.channels/has-rgb-heads`)
- `afterglow.effects.oscillators/sawtooth-beat` (instead use `sawtooth`)
- `afterglow.effects.oscillators/sawtooth-bar` (instead use `sawtooth` with `:interval :bar`)
- `afterglow.effects.oscillators/sawtooth-phrase` (instead use `sawtooth` with `:interval :phrase`)
- `afterglow.effects.oscillators/triangle-beat` (instead use `triangle`)
- `afterglow.effects.oscillators/triangle-bar` (instead use `triangle` with `:interval :bar`)
- `afterglow.effects.oscillators/triangle-phrase` (instead use `triangle` with `:interval :phrase`)
- `afterglow.effects.oscillators/square-beat` (instead use `square`)
- `afterglow.effects.oscillators/square-bar` (instead use `square` with `:interval :bar`)
- `afterglow.effects.oscillators/square-phrase` (instead use `square` with `:interval :phrase`)
- `afterglow.effects.oscillators/sine-beat` (instead use `sine`)
- `afterglow.effects.oscillators/sine-bar` (instead use `sine` with `:interval :bar`)
- `afterglow.effects.oscillators/sine-phrase` (instead use `sine` with `:interval :phrase`)
- `afterglow.effects.params/build-oscillated-param` (instead use
`afterglow.effects.oscillators/build-oscillated-param`)
- `afterglow.show/add-midi-control-to-cue-mapping` (instead use
`afterglow.effects.cues/add-midi-to-cue-mapping`)
- `afterglow.show/remove-midi-control-to-cue-mapping` (instead use
`afterglow.effects.cues/remove-midi-to-cue-mapping`)
- The detailed documentation was updated to use attributes to link to the API
documentation so it could be linked to its release-specific version.
- The API documentation was moved into a github-pages branch so
versioned snapshots can be kept around.
- A few functions were newly deprecated to improve the consistency of the API:
- `afterglow.effects.cues/add-midi-control-to-cue-mapping` (instead
use the more accurately named `add-midi-to-cue-mapping`)
- `afterglow.effects.cues/remove-midi-control-to-cue-mapping`
(instead use the more accurately named `remove-midi-to-cue-mapping`)
- `afterglow.show/remove-midi-control-to-var-mapping`,
`afterglow.show/remove-midi-control-to-master-mapping`, and
`afterglow.show/remove-midi-control-metronome-mapping` (they are
no longer needed, the general-purpose function
`afterglow.midi/remove-control-mapping` works instead of each)
## [0.1.6] - 2016-01-11
### Added
- Support for [CoreMIDI4J](https://github.com/DerekCook/CoreMidi4J),
to preferentially use MIDI devices returned by this new lightweight
open-source Java MIDI service provider implementation for Mac OS X.
CoreMIDI4J is compatible with current Java and OS versions, and
addresses long-standing defects in the standard Java MIDI
implementation, such as support for System Exclusive messages, and
reconfiguration of the MIDI environment as devices are connected and
disconnected. Afterglow's MIDI implementation now gracefully handles
changes in the MIDI environment, cleaning up bindings, synced
metronomes, grid controllers, and cue feedback functions associated
with devices which no longer exist, and making new devices available
for use.
- MIDI device watchers, which can set up bindings whenever a specified
device is connected. These also allow effortless recovery from a
temporary disconnection from a device during a show.
- Code cues, making it easy to trigger arbitrary activity from a cue
grid, [issue 34](https://github.com/Deep-Symmetry/afterglow/issues/34).
- Links to graphs and expanded discussion in the oscillator API docs.
- Dimmer effects can now work with dimmer function ranges on
multipurpose channels as well as full dedicated dimmer channels.
- Dimmer effects can now also create virtual dimmers for RGB-mixing
fixtures that don't have any actual dimmer channels, allowing them
to participate as if they did, by modifying the color effects being
sent to them.
- Step parameters can now have interval ratios, like oscillators.
- When building step parameters, you can now use dynamic parameters as
inputs.
- When mapping a MIDI control to a show variable, you can now supply a
custom function to transform the incoming value into whatever you
need it to be,
[issue 32](https://github.com/Deep-Symmetry/afterglow/issues/32).
- When mapping a midi control to launch a cue, if your controller
supports velocity (and perhaps also aftertouch, or polyphonic key
pressure), you can have those values affect cue variables which have
been defined as velocity sensitive, in the same way that Ableton
Push pads do.
- A variation of the sparkle effect which uses dimmer channels,
[issue 35](https://github.com/Deep-Symmetry/afterglow/issues/35).
- Some more examples of how to get started working with Afterglow.
- A variety of other documentation improvements.
### Changed
- Oscillators have been completely redesigned in order to be more
flexible and easy to create and work with, and to support dynamic
parameters so their configuration can vary over time or location,
[issue 9](https://github.com/Deep-Symmetry/afterglow/issues/9). The old
oscillator and oscillated parameter functions have been deprecated,
and are now stubs wich delegate to the new implementation. They will
be removed in an upcoming release.
- The functions `add-midi-control-to-cue-mapping` and
`remove-midi-control-to-cue-mapping` have been moved from the
`afterglow.show` namespace to `afterglow.effects.cues`, to solve a
circular dependency conflict which arose in implementing velocity
and aftertouch support. There are stubs in the old location which
delegate to the new ones, but they are less efficient than calling
them in the new location directly, and are deprecated. The stubs
will be removed in an upcoming release.
- The former `IHeadParam` interface has been eliminated, folding its
semantics into the `IParam` interface, and simplifying the
implementation of dynamic parameters,
[issue 20](https://github.com/Deep-Symmetry/afterglow/issues/20).
- The `:adjust-fn` parameter to `build-variable-param` has been
renamed `:transform-fn` to be consistent with the equivalent
mechanism added for MIDI control mappings in
[issue 32](https://github.com/Deep-Symmetry/afterglow/issues/32). The
documentation has been improved a bit as well.
- The maps which track MIDI bindings now use the underlying Java
`MidiDevice` object for their keys, which allows for more efficent
lookup than the `overtone.midi` `:midi-device` map which was
previously used.
- The functions which add and remove bindings to MIDI control, note,
and aftertouch messages have been simplified so they no longer
require you to come up with a unique keyword to use when later
removing the binding. Instead, you simply pass the same function
that was used when establishing the binding to remove it.
- All functions which allow you to select a MIDI device have been made
consistent, and now allow you to filter devices by a variety of
criteria, not just the name and description.
- Various maps used to manage Afterglow state, such as shows, cue
grids, Push controllers and auto-binding watchers, are now tagged
with type metadata to make it easier to recognize them.
### Fixed
- Clicking on the BPM slider in the web interface now updates the BPM
(previously you had to actually drag it),
[issue 18](https://github.com/Deep-Symmetry/afterglow/issues/18).
- Launching `:held` cues from generic MIDI controllers, the Ableton
Push, and the web interface, would not succeed if the previous
effect created by the cue was still in the process of ending,
[issue 33](https://github.com/Deep-Symmetry/afterglow/issues/33).
- Make sure MIDI inputs are connected when `sync-to-midi-clock` is
called,
[issue 10](https://github.com/Deep-Symmetry/afterglow/issues/10).
- Also make sure the MIDI inputs are opened when rendering the web UI,
so that the sync button will be able to list available sources of
MIDI clock messages.
- Clarified that syncing to Traktor beat phase still requires Traktor
to be configured to send MIDI clock,
[issue 37](https://github.com/Deep-Symmetry/afterglow/issues/37).
- Added more detail about how to safely import and configure the
Afterglow Traktor device mapping.
- A variety of issues ranging from questionable style through misplaced
documentation, unused or inaccessible code, preconditions that would
not take effect, and actual problems, were identified by Kibit and
Eastwood (after discovering how to work around a crash in Eastwood
caused by the protocol definitions in `rhythm.clj`), were cleaned
up.
## [0.1.5] - 2015-11-25
### Added
- Chases, which support sequences of effects with a variety of timing,
fade, and looping options.
- Step parameters, which provide flexible control of chases.
- Pan/Tilt effects, which work at a lower level than direction
effects, but since they are closer to the physical capabilities of
the lights, can be helpful in creating natural and intuitive
movements. They also help avoid issues with geometric singularities
when fading between different directions.
- Graphs to visually illustrate the available oscillators and the
parameters that tune their behavior.
### Changed
- Fades now delegate their notion of ending to the underlying effects
which are being faded between, and pass end requests along to them.
- Stopped embedding `cider-nrepl` because it added too much bloat and
complexity for an unlikely use case. If you want to work with CIDER
for live-coding with Afterglow, launch it from a project, rather
than as an überjar.
### Fixed
- Some MIDI controllers (perhaps those which sent messages on channels
other than 0?) were causing Overtone's
[midi-clj](https://github.com/overtone/midi-clj) library to create
message maps with `nil` values for the `:status` key when sending
control-change or note messages, which was preventing them from
being detected or processed correctly. Afterglow now always looks
for command-like messages via the `:command` key instead,
[issue 8](https://github.com/Deep-Symmetry/afterglow/issues/8).
- Fading colors in and out from nothing, as represented by a `nil`
assignment value, was fading to a desaturated version of black,
which does not lead to the kind of results people generally expect
and want. In this situation, the color is now faded to or from a
darkened version of itself.
- The phases of the square-bar and square-phrase oscillators were
flipped from what they should be according to the documentation, and
compared with square-beat. This was discovered and corrected when
graphing them.
- Calculation of white LED channel for colors with lightness less than
50 was wrong, leading to slight unintentional desaturation of
colors.
- It was a little too hard to see the difference between the "ready"
and "active" states for some colors on the Ableton Push after
introducing full RGB button color support; they are once again more
visually distinct.
- Preconditions in channel-creation functions for fixture definitions
were mal-formed, and so were not actually validating the function
arguments.
- Parts of the introductory walk-through in `README.md` had become
stale and needed to be updated.
- The API documentation for `patch-fixture!` was fleshed out.
## [0.1.4] - 2015-09-27
### Added
- Support for inverted dimmers (where lower DMX values are brighter).
- Scenes, which allow multiple effects to be grouped into one.
- A framework for fading between effect elements, with sensible
semantics for colors, aim, directions, and functions, and defaults
when fading in or out of nothing.
- Fading between entire effects, including complex effects and scenes,
which do not necessarily affect all the same fixtures and channels.
- A new mechanism for extending the rendering loop to support effects
which do not result in DMX values to send to the show universes.
- Support for (and examples of) integration with laser shows being run
by Pangolin Beyond software, using the extension mechanism.
- New conditional effects and variable-setting effects, using the
extension mechanism.
- A composable effect which can transform the colors being created by
other effects to build layered looks. The default transformation
causes the colors to range from fully saturated at the start of each
beat to pure gray by the end of the beat, but it is very easy to
swap in other transformations using oscillated parameters.
- Holding down the Shift key while turning the encoder allows the BPM
to be changed more rapidly (in whole beat increments, rather than
tenths) on the Ableton Push.
- Fixture definitions for Chauvet LED Techno Strobe, LED Techno Strobe
RGB, ColorStrip, Spot LED 150, Kinta X, Scorpion Storm FX RGB,
Scorpion Storm RGX, Q-Spot 160, Intimidator Scan LED 300, Geyser RGB
fogger, and Hurricane 1800 Flex fogger.
- Example effect which desaturates a rainbow over the course of a
beat.
### Changed
- Improved readability and parallelism of core rendering loop.
- The default frame rate was raised from 30Hz to 40Hz.
- Ableton Push now uses SysEx message to specify the exact RGB color
to light up a pad, rather than choosing from the limited set
available through MIDI velocity.
- Ableton Push now makes sure the pads are put in poly-pressure mode,
and sets the sensitivity level to reduce the chance of stuck pads.
- The stability of MIDI clock sync was greatly improved, in order to
facilitate the Beyond integration.
- The refresh rates of the Push and web interfaces were reduced to put
less load on the CPU.
- The tempo buttons on the Push and web interfaces are now always
flashed at least once per beat, even if the reduced refresh rate
causes the normal "on" window to be missed.
- Improved content and format of command-line usage help.
### Fixed
- The Ableton Push binding now ends cues when it receives an afertouch
value of 0, since the hardware is not reliably sending a note-end
message, especially when multiple pads are being pressed at once.
- Fail gracefully when trying to bind to an Ableton Push when none can
be found.
- Some small errors in the documentation were corrected.
## [0.1.3] - 2015-08-16
### Added
- Ability to
[translate](https://github.com/Deep-Symmetry/afterglow/blob/master/doc/fixture_definitions.adoc#translating-qlc-fixture-definitions)
fixture definitions from the format used by
[QLC+](http://www.qlcplus.org/) to help people get started on
defining fixtures.
### Changed
- Separated OLA communication into its own project,
[ola-clojure](https://github.com/Deep-Symmetry/ola-clojure#ola-clojure).
## [0.1.2] - 2015-08-09
### Added
- Allow configuration of an alternate host for the OLA daemon
(primarily for Windows users, since there is not yet a Windows port
of OLA).
- Flesh out the command-line arguments when running as an executable
jar.
- Allow a list of files to be loaded at startup when running as an
executable jar, in order to configure fixtures, shows, effects, and
cues.
- Support syncing to Traktor’s beat grid with the help of a new custom
controller mapping.
### Changed
- MIDI sync sources are now always watched for, and can be offered to
the user without pausing first.
## [0.1.1] - 2015-08-02
### Added
- Ability to register for notification about changes in status of cues
and values of show variables.
- Creating a show can optionally register it with the web interface by
passing a description with `:description`.
- Now cleans up thread-local bindings stored by the web REPL when
sessions time out.
### Changed
- Forked Protobuf related libraries to make them build Java 6
compatible clases, so
[afterglow-max](https://github.com/Deep-Symmetry/afterglow-max) can run
inside the Java environment provided by
[Cycling ‘74’s Max](https://cycling74.com/).
- Made the meaning of the `:start` attribute of cue variables simpler
and more consistent.
- Cue variables which respond to aftertouch now also respond to
initial velocity, and the related configuration attributes have been
renamed to `:velocity` to reflect this increased generality.
- Improved the detection of project name and version number so they
work for afterglow-max builds too.
### Fixed
- Eliminated crashes in the Ableton Push interface when trying to
adjust the value of a cue variable which had not yet been set to
anything.
## 0.1.0 - 2015-07-19
### Added
- Initial Public Release
[unreleased]: https://github.com/Deep-Symmetry/afterglow/compare/v0.2.5...HEAD
[0.2.5]: https://github.com/Deep-Symmetry/afterglow/compare/v0.2.4...v0.2.5
[0.2.4]: https://github.com/Deep-Symmetry/afterglow/compare/v0.2.3...v0.2.4
[0.2.3]: https://github.com/Deep-Symmetry/afterglow/compare/v0.2.2...v0.2.3
[0.2.2]: https://github.com/Deep-Symmetry/afterglow/compare/v0.2.1...v0.2.2
[0.2.1]: https://github.com/Deep-Symmetry/afterglow/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/Deep-Symmetry/afterglow/compare/v0.1.6...v0.2.0
[0.1.6]: https://github.com/Deep-Symmetry/afterglow/compare/v0.1.5...v0.1.6
[0.1.5]: https://github.com/Deep-Symmetry/afterglow/compare/v0.1.4...v0.1.5
[0.1.4]: https://github.com/Deep-Symmetry/afterglow/compare/v0.1.3...v0.1.4
[0.1.3]: https://github.com/Deep-Symmetry/afterglow/compare/v0.1.2...v0.1.3
[0.1.2]: https://github.com/Deep-Symmetry/afterglow/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/Deep-Symmetry/afterglow/compare/v0.1.0...v0.1.1
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at james@deepsymmetry.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
Hi there! We're delighted that you'd like to contribute to this
project. It has been generous collaboration from people all over the
world that has made it possible so far, and your help is key to
keeping it great.
Contributions to this project are [released][contributions-released]
to the public under the [project's open source license](LICENSE).
This project adheres to the
[Contributor Covenant Code of Conduct][covenant].
By participating, you are expected to uphold this code.
## Getting started
Before you can start contributing to Afterglow, you'll need to
set up your environment. Fork and clone the repo and install
[Clojure][clojure] (for compiling and running the code) and
[Leiningen](https://leiningen.org) (to manage the project dependencies
and builds). Both of these rely on having a working Java runtime; if
the mechanism you used to install them did not automatically include
one, I recommend installing a current version of the
[OpenJDK](http://openjdk.java.net).
> Starting with version 0.2.5, you also need to have
> [Antora](https://antora.org) installed because it is used to build
> the embedded copy of the User Guide when building and running from
> source. Assuming you have a current version of `node` installed,
> simply running `npm i` inside the top level directory of the
> afterglow project will install Antora for you. If you don't have a
> node environment and are impatient to get started without getting
> one, you can temporarily comment out the section of `project.clj`
> that builds the user guide. Add `#_` to the `:prep-tasks`
> line so that it looks like this:
>
> ` :prep-tasks [#_["shell" "npx" "antora" "--fetch" "doc/embedded.yml"`
>
> But keep in mind that if you do this, the built-in user guide will
> not work, and you will not be able to create a release-worthy build
> until you install Antora and restore the line to its un-commented
> state.
Once you have those in place, you can run Afterglow from source by
opening a terminal window inside your clone of the project, and typing
`lein repl`. You will see a bunch of output as the embedded copy of
the User Guide is generated by Antora and the images are copied in,
and then Clojure will start, with output similar to this:
REPL-y 0.5.1, nREPL 0.8.3
Clojure 1.10.3
OpenJDK 64-Bit Server VM 11.0.10+9-LTS
afterglow loaded.
afterglow.examples=>
At that point you can follow along with the example flow on the main
project page, and start diving into exploring and changing the source
code.
> :wrench: If you are just building Afterglow to work around the
> current [issue](https://github.com/clojars/clojars-web/issues/195)
> preventing current versions from being available through Clojars, at
> this point you can run `lein install`, which will build and install
> the library in your local Maven repository, so other projects can
> depend on the version that you just built without needing to find it
> on Clojars.
Of course to do any serious work, you will want some sort of editor
with embedded REPL support, and ideally structural editing support for
Lisp s-expressions. We find [GNU Emacs][emacs] with [CIDER][cider] to
be an incredibly productive environment for Clojure work, but some of
our colleagues swear by [IntelliJ IDEA][idea] (even the free Community
Edition) with [Cursive][cursive], so use whatever IDE or editor works
best for you.
For testing you are going to want to install OLA (as described on the
main project page) and have some lighting (and possibly MIDI) hardware
to attach.
## Giving back
Once you have something working you’d like to share, you can open a
[pull request][pulls].
Or if you simply have an idea, or something that you wish worked
differently, feel free to open an [issue][issues] if it seems like
nobody already has.
## Maintainers
Afterglow is primarily maintained by [@brunchboy][brunchboy].
## License
<a href="http://deepsymmetry.org"><img align="right" alt="Deep Symmetry"
src="doc/modules/ROOT/assets/images/DS-logo-github.png" width="250" height="150"></a>
Copyright © 2016–2022 [Deep Symmetry, LLC](http://deepsymmetry.org)
Distributed under the
[Eclipse Public License 2.0](https://opensource.org/licenses/EPL-2.0)
By using this software in any fashion, you are agreeing to be bound by
the terms of this license. You must not remove this notice, or any
other, from this software.
[contributions-released]: https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license
[covenant]: http://contributor-covenant.org/
[clojure]: https://clojure.org
[leiningen]: https://leiningen.org
[emacs]: https://www.gnu.org/software/emacs/
[cider]: http://www.cider.mx/en/latest/
[idea]: https://www.jetbrains.com/idea/
[cursive]: https://cursive-ide.com
[pulls]: https://github.com/Deep-Symmetry/afterglow/pulls
[issues]: https://github.com/Deep-Symmetry/afterglow/issues
[brunchboy]: https://github.com/brunchboy
================================================
FILE: LICENSE
================================================
Eclipse Public License - v 2.0
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION
OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
1. DEFINITIONS
"Contribution" means:
a) in the case of the initial Contributor, the initial content
Distributed under this Agreement, and
b) in the case of each subsequent Contributor:
i) changes to the Program, and
ii) additions to the Program;
where such changes and/or additions to the Program originate from
and are Distributed by that particular Contributor. A Contribution
"originates" from a Contributor if it was added to the Program by
such Contributor itself or anyone acting on such Contributor's behalf.
Contributions do not include changes or additions to the Program that
are not Modified Works.
"Contributor" means any person or entity that Distributes the Program.
"Licensed Patents" mean patent claims licensable by a Contributor which
are necessarily infringed by the use or sale of its Contribution alone
or when combined with the Program.
"Program" means the Contributions Distributed in accordance with this
Agreement.
"Recipient" means anyone who receives the Program under this Agreement
or any Secondary License (as applicable), including Contributors.
"Derivative Works" shall mean any work, whether in Source Code or other
form, that is based on (or derived from) the Program and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship.
"Modified Works" shall mean any work in Source Code or other form that
results from an addition to, deletion from, or modification of the
contents of the Program, including, for purposes of clarity any new file
in Source Code form that contains any contents of the Program. Modified
Works shall not include works that contain only declarations,
interfaces, types, classes, structures, or files of the Program solely
in each case in order to link to, bind by name, or subclass the Program
or Modified Works thereof.
"Distribute" means the acts of a) distributing or b) making available
in any manner that enables the transfer of a copy.
"Source Code" means the form of a Program preferred for making
modifications, including but not limited to software source code,
documentation source, and configuration files.
"Secondary License" means either the GNU General Public License,
Version 2.0, or any later versions of that license, including any
exceptions or additional permissions as identified by the initial
Contributor.
2. GRANT OF RIGHTS
a) Subject to the terms of this Agreement, each Contributor hereby
grants Recipient a non-exclusive, worldwide, royalty-free copyright
license to reproduce, prepare Derivative Works of, publicly display,
publicly perform, Distribute and sublicense the Contribution of such
Contributor, if any, and such Derivative Works.
b) Subject to the terms of this Agreement, each Contributor hereby
grants Recipient a non-exclusive, worldwide, royalty-free patent
license under Licensed Patents to make, use, sell, offer to sell,
import and otherwise transfer the Contribution of such Contributor,
if any, in Source Code or other form. This patent license shall
apply to the combination of the Contribution and the Program if, at
the time the Contribution is added by the Contributor, such addition
of the Contribution causes such combination to be covered by the
Licensed Patents. The patent license shall not apply to any other
combinations which include the Contribution. No hardware per se is
licensed hereunder.
c) Recipient understands that although each Contributor grants the
licenses to its Contributions set forth herein, no assurances are
provided by any Contributor that the Program does not infringe the
patent or other intellectual property rights of any other entity.
Each Contributor disclaims any liability to Recipient for claims
brought by any other entity based on infringement of intellectual
property rights or otherwise. As a condition to exercising the
rights and licenses granted hereunder, each Recipient hereby
assumes sole responsibility to secure any other intellectual
property rights needed, if any. For example, if a third party
patent license is required to allow Recipient to Distribute the
Program, it is Recipient's responsibility to acquire that license
before distributing the Program.
d) Each Contributor represents that to its knowledge it has
sufficient copyright rights in its Contribution, if any, to grant
the copyright license set forth in this Agreement.
e) Notwithstanding the terms of any Secondary License, no
Contributor makes additional grants to any Recipient (other than
those set forth in this Agreement) as a result of such Recipient's
receipt of the Program under the terms of a Secondary License
(if permitted under the terms of Section 3).
3. REQUIREMENTS
3.1 If a Contributor Distributes the Program in any form, then:
a) the Program must also be made available as Source Code, in
accordance with section 3.2, and the Contributor must accompany
the Program with a statement that the Source Code for the Program
is available under this Agreement, and informs Recipients how to
obtain it in a reasonable manner on or through a medium customarily
used for software exchange; and
b) the Contributor may Distribute the Program under a license
different than this Agreement, provided that such license:
i) effectively disclaims on behalf of all other Contributors all
warranties and conditions, express and implied, including
warranties or conditions of title and non-infringement, and
implied warranties or conditions of merchantability and fitness
for a particular purpose;
ii) effectively excludes on behalf of all other Contributors all
liability for damages, including direct, indirect, special,
incidental and consequential damages, such as lost profits;
iii) does not attempt to limit or alter the recipients' rights
in the Source Code under section 3.2; and
iv) requires any subsequent distribution of the Program by any
party to be under a license that satisfies the requirements
of this section 3.
3.2 When the Program is Distributed as Source Code:
a) it must be made available under this Agreement, or if the
Program (i) is combined with other material in a separate file or
files made available under a Secondary License, and (ii) the initial
Contributor attached to the Source Code the notice described in
Exhibit A of this Agreement, then the Program may be made available
under the terms of such Secondary Licenses, and
b) a copy of this Agreement must be included with each copy of
the Program.
3.3 Contributors may not remove or alter any copyright, patent,
trademark, attribution notices, disclaimers of warranty, or limitations
of liability ("notices") contained within the Program from any copy of
the Program which they Distribute, provided that Contributors may add
their own appropriate notices.
4. COMMERCIAL DISTRIBUTION
Commercial distributors of software may accept certain responsibilities
with respect to end users, business partners and the like. While this
license is intended to facilitate the commercial use of the Program,
the Contributor who includes the Program in a commercial product
offering should do so in a manner which does not create potential
liability for other Contributors. Therefore, if a Contributor includes
the Program in a commercial product offering, such Contributor
("Commercial Contributor") hereby agrees to defend and indemnify every
other Contributor ("Indemnified Contributor") against any losses,
damages and costs (collectively "Losses") arising from claims, lawsuits
and other legal actions brought by a third party against the Indemnified
Contributor to the extent caused by the acts or omissions of such
Commercial Contributor in connection with its distribution of the Program
in a commercial product offering. The obligations in this section do not
apply to any claims or Losses relating to any actual or alleged
intellectual property infringement. In order to qualify, an Indemnified
Contributor must: a) promptly notify the Commercial Contributor in
writing of such claim, and b) allow the Commercial Contributor to control,
and cooperate with the Commercial Contributor in, the defense and any
related settlement negotiations. The Indemnified Contributor may
participate in any such claim at its own expense.
For example, a Contributor might include the Program in a commercial
product offering, Product X. That Contributor is then a Commercial
Contributor. If that Commercial Contributor then makes performance
claims, or offers warranties related to Product X, those performance
claims and warranties are such Commercial Contributor's responsibility
alone. Under this section, the Commercial Contributor would have to
defend claims against the other Contributors related to those performance
claims and warranties, and if a court requires any other Contributor to
pay any damages as a result, the Commercial Contributor must pay
those damages.
5. NO WARRANTY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS"
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF
TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
PURPOSE. Each Recipient is solely responsible for determining the
appropriateness of using and distributing the Program and assumes all
risks associated with its exercise of rights under this Agreement,
including but not limited to the risks and costs of program errors,
compliance with applicable laws, damage to or loss of data, programs
or equipment, and unavailability or interruption of operations.
6. DISCLAIMER OF LIABILITY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS
SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
PROFITS), 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 OR DISTRIBUTION OF THE PROGRAM OR THE
EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
7. GENERAL
If any provision of this Agreement is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of
the remainder of the terms of this Agreement, and without further
action by the parties hereto, such provision shall be reformed to the
minimum extent necessary to make such provision valid and enforceable.
If Recipient institutes patent litigation against any entity
(including a cross-claim or counterclaim in a lawsuit) alleging that the
Program itself (excluding combinations of the Program with other software
or hardware) infringes such Recipient's patent(s), then such Recipient's
rights granted under Section 2(b) shall terminate as of the date such
litigation is filed.
All Recipient's rights under this Agreement shall terminate if it
fails to comply with any of the material terms or conditions of this
Agreement and does not cure such failure in a reasonable period of
time after becoming aware of such noncompliance. If all Recipient's
rights under this Agreement terminate, Recipient agrees to cease use
and distribution of the Program as soon as reasonably practicable.
However, Recipient's obligations under this Agreement and any licenses
granted by Recipient relating to the Program shall continue and survive.
Everyone is permitted to copy and distribute copies of this Agreement,
but in order to avoid inconsistency the Agreement is copyrighted and
may only be modified in the following manner. The Agreement Steward
reserves the right to publish new versions (including revisions) of
this Agreement from time to time. No one other than the Agreement
Steward has the right to modify this Agreement. The Eclipse Foundation
is the initial Agreement Steward. The Eclipse Foundation may assign the
responsibility to serve as the Agreement Steward to a suitable separate
entity. Each new version of the Agreement will be given a distinguishing
version number. The Program (including Contributions) may always be
Distributed subject to the version of the Agreement under which it was
received. In addition, after a new version of the Agreement is published,
Contributor may elect to Distribute the Program (including its
Contributions) under the new version.
Except as expressly stated in Sections 2(a) and 2(b) above, Recipient
receives no rights or licenses to the intellectual property of any
Contributor under this Agreement, whether expressly, by implication,
estoppel or otherwise. All rights in the Program not expressly granted
under this Agreement are reserved. Nothing in this Agreement is intended
to be enforceable by any entity that is not a Contributor or Recipient.
No third-party beneficiary rights are created under this Agreement.
Exhibit A - Form of Secondary Licenses Notice
"This Source Code may also be made available under the following
Secondary Licenses when the conditions for such availability set forth
in the Eclipse Public License, v. 2.0 are satisfied: {name license(s),
version(s), and exceptions or additional permissions here}."
Simply including a copy of this Agreement, including this Exhibit A
is not sufficient to license the Source Code under Secondary Licenses.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to
look for such a notice.
You may add additional accurate notices of copyright ownership.
================================================
FILE: README.md
================================================
# Afterglow
[](https://deep-symmetry.zulipchat.com/#narrow/stream/318697-afterglow)
<image align="right" width="275" height="255"
src="doc/modules/ROOT/assets/images/Afterglow-logo-padded-left.png"> <br/><br/> An
environment supporting
[live coding](https://en.wikipedia.org/wiki/Live_coding) for the
creation of algorithmic light shows in [Clojure](http://clojure.org),
leveraging the
[Open Lighting Architecture](https://www.openlighting.org/ola/) with
the help of
[ola-clojure](https://github.com/Deep-Symmetry/ola-clojure#ola-clojure),
[wayang](https://github.com/Deep-Symmetry/wayang#wayang),
[beat-link](https://github.com/Deep-Symmetry/beat-link), and pieces of
the [Overtone](http://overtone.github.io) toolkit. Beyond building on
pieces of Overtone, the entire Afterglow project was
[inspired](https://vimeo.com/22798433) by it.
[](#license)
### Documentation Overview
This page provides an introduction in how to install and use
Afterglow. The [Developer
Guide](https://afterglow-guide.deepsymmetry.org) goes much deeper,
and there is also [API
documentation](http://afterglow-guide.deepsymmetry.org/api/). For more
interactive help, the [Afterglow stream on
Zulip](https://deep-symmetry.zulipchat.com/#narrow/stream/318697-afterglow) is the place to start,
and if you want to see (or contribute to) more structured and lasting
community-driven documentation, there is also a project
[wiki](https://github.com/Deep-Symmetry/afterglow/wiki).
## Why Explore Afterglow?
> tl;dr—show me? Check out the
> [Show Control pics](https://afterglow-guide.deepsymmetry.org/afterglow/README.html#show-control)
> and [performance video](https://afterglow-guide.deepsymmetry.org/afterglow/videos.html).
As suggested by the live-coding orientation mentioned above, which is
designed to let you inject your own code right into the frame
rendering process, Afterglow takes a very different approach to
controlling light shows than other software. It won’t be right
for everyone, but will be extremely compelling to a particular niche.
The early stages of its [rendering
loop](https://afterglow-guide.deepsymmetry.org/afterglow/rendering_loop.html#the-rendering-loop)
can offer higher levels of abstraction than the usual DMX [channel
value](https://afterglow-guide.deepsymmetry.org/afterglow/effects.html#channel-effects)
or [fixture
function](https://afterglow-guide.deepsymmetry.org/afterglow/effects.html#function-effects)
(although those are fully supported too):
* You can express your desired results in terms of an abstract
[color](https://afterglow-guide.deepsymmetry.org/afterglow/effects.html#color-effects),
including support for the hue-saturation-lightness model, which is
great for algorithmic looks, and have it translated to whatever
color channels (or color wheel) your fixture supports.
* Groups of moving heads can be told to face particular
[directions](https://afterglow-guide.deepsymmetry.org/afterglow/effects.html#direction-effects)
by specifying parameterized vectors, or to
[aim](https://afterglow-guide.deepsymmetry.org/afterglow/effects.html#aim-effects)
at a particular point in space, and Afterglow figures out how to
translate that into DMX control values given its understanding of
the
[fixture](https://afterglow-guide.deepsymmetry.org/afterglow/fixture_definitions.html)
and
[where](https://afterglow-guide.deepsymmetry.org/afterglow/show_space.html),
and at what angle, you hung it.
* There are a variety of
[oscillators](https://afterglow-guide.deepsymmetry.org/afterglow/oscillators.html)
which can efficiently drive effect parameters.
* You can also create
[complex effects](https://afterglow-guide.deepsymmetry.org/afterglow/effects.html#complex-effects),
with
[adjustable parameters](https://afterglow-guide.deepsymmetry.org/afterglow/parameters.html)
that can be controlled through a rich binding to an
[Ableton Push](https://afterglow-guide.deepsymmetry.org/afterglow/push2.html) or
[Novation Launchpad family](https://afterglow-guide.deepsymmetry.org/afterglow/launchpad.html)
controller, or via
[Open Sound Control](https://afterglow-guide.deepsymmetry.org/afterglow/mapping_sync.html#open-sound-control)
(OSC)—even wirelessly from a tablet or smartphone.
* The timing of effects is pervasively influenced by a deep notion of
[musical time](https://afterglow-guide.deepsymmetry.org/afterglow/metronomes.html),
with support for synchronization via
[MIDI clock](https://afterglow-guide.deepsymmetry.org/afterglow/mapping_sync.html#syncing-to-midi-clock),
[Traktor Beat Phase](https://afterglow-guide.deepsymmetry.org/afterglow/mapping_sync.html#syncing-to-traktor-beat-phase),
or Pioneer
[Pro DJ Link](https://afterglow-guide.deepsymmetry.org/afterglow/mapping_sync.html#syncing-to-pro-dj-link)
beat grids.
* You can even host Afterglow within
[Cycling ‘74’s Max](https://cycling74.com/) visual interactive
environment.
If any of this sounds interesting to you, read on to see how to get
started!
## Table of Contents
* [Why Explore Afterglow?](#why-explore-afterglow)
* [Installation](#installation)
* [Status](#status)
* [Getting Help](#getting-help)
* [Usage](#usage)
* [Troubleshooting](#troubleshooting)
* [Bugs](#bugs)
* [What Next?](#what-next)
* [Release Checklist](#release-checklist)
* [Tasks](#tasks)
* [License](#license)
## Installation
1. [Install OLA](https://www.openlighting.org/ola/getting-started/downloads/).
(On the Mac I recommend using [Homebrew](http://brew.sh) which lets you simply
`brew install ola`). Once you launch the `olad` server you can
interact with its embedded
[web server](http://localhost:9090/ola.html), which is very helpful
in seeing whether anything is working; you can even watch live DMX
values changing.
> :wrench: If you are installing Afterglow on Windows, see the
> [Wiki discussion](https://github.com/Deep-Symmetry/afterglow/wiki/Questions#ola-and-windows)
> about OLA options.
2. For now set up a Clojure project using [Leiningen](http://leiningen.org).
3. Add this project as a dependency:
[](http://clojars.org/afterglow)
> :wrench: If you were using older releases of Afterglow and installed
> [CoreMIDI4J](https://github.com/DerekCook/CoreMidi4J) in
> `/Library/Java/Extensions`, you need to remove it, because Afterglow
> now embeds an improved version and uses it when necessary.
If you want to run Afterglow as a standalone executable, you can
download the executable überjar from the
[releases](https://github.com/Deep-Symmetry/afterglow/releases) page.
[](https://github.com/Deep-Symmetry/afterglow/releases)
For an example of a project which uses Afterglow as a dependency, as
described above, see
[afterglow-max](https://github.com/Deep-Symmetry/afterglow-max#afterglow-max),
which hosts Afterglow inside [Cycling ‘74’s Max](https://cycling74.com/).
## Status
Although Afterglow is far from finished, it’s ready for the world to
start exploring, and helping decide directions in which to grow next
(as well as identifying areas where the documentation needs
clarification or reinforcement).
Most of the crazy ideas have panned out and been implemented, and I am
fleshing out the basic details needed for everyday use. The examples
are starting to be intriguing and informative, and the [Developer
Guide](https://afterglow-guide.deepsymmetry.org) is getting
substantial. The modeling of fixtures, channels, etc. is coming
together nicely, though there may be a few more changes.
There is now an embedded web application, which is growing into a show
control interface for people who are not Clojure hackers, and a useful
adjunct to the Ableton Push and Launchpad family control surface
interfaces. Each is explained in the documentation link above.
Afterglow also includes the beginnings of a show visualizer for
designing and working on effects without having to physically hook up
lights (a proof of concept, really, at this point). This is
implemented in WebGL using a volumetric ray tracer and looks quite
promising, at least for a small number of fixtures; it will probably
overwhelm the graphics processor on most systems once you add too many
lights. However, the framework can be used by someone who actually
knows OpenGL programming to build a more scalable preview (albeit one
that probably doesn’t look quite so photo-realistic with beams
impacting drifting fog). This is an area where I would love some help
if it sounds interesting!
## Getting Help
<a href="http://zulip.com"><img align="right" alt="Zulip logo"
src="doc/modules/ROOT/assets/images/zulip-icon-circle.svg"
width="128" height="128"></a>
Deep Symmetry’s projects are generously sponsored with hosting by <a
href="https://zulip.com">Zulip</a>, an open-source modern team chat
app designed to keep both live and asynchronous conversations
organized. Thanks to them, you can <a
href="https://deep-symmetry.zulipchat.com/#narrow/stream/318697-afterglow">chat
with our community</a>, ask questions, get inspiration, and share your
own ideas.
## Usage
> The rest of this document primarily provides an introduction to the
> configuration of Afterglow from the command line and text files. The
> show control interface is explained in the
> [web](https://afterglow-guide.deepsymmetry.org/afterglow/README.html#web-ui)
> and
> [Push](https://afterglow-guide.deepsymmetry.org/afterglow/push2.html)
> sections.
Although you will often want to use Afterglow from a Clojure repl, you
can also bring it up as an executable jar, and run it using `java
-jar` with command-line arguments:
```
> java -jar afterglow.jar --help
afterglow 0.2.4, a live-coding environment for light shows.
Usage: afterglow [options] [init-file ...]
Any init-files specified as arguments will be loaded at startup,
in the order they are given, before creating any embedded servers.
Options:
-w, --web-port PORT 16000 Port number for web UI
-n, --no-browser Don't launch web browser
-o, --osc-port PORT 16001 Port number for OSC server
-r, --repl-port PORT Port number for REPL, if desired
-l, --log-file PATH logs/afterglow.log File into which log is written
-H, --olad-host HOST localhost Host name or address of OLA daemon
-P, --olad-port PORT 9010 Port number OLA daemon listens on
-q, --convert-qxf PATH Convert QLC+ fixture file and exit
-h, --help Display help information and exit
If you translate a QLC+ fixture definition file, Afterglow will try to write
its version in the same directory, but won't overwrite an existing file.
If you do not explicitly specify a log file, and Afterglow cannot write to
the default log file path, logging will be silently suppressed.
Please see https://github.com/Deep-Symmetry/afterglow for more information.
```
As noted, you can pass a list of init-files when you run Afterglow
this way, which gives you the opportunity to set up the actual
universes, fixtures, effects, and cues that you want to use in your
show. As a starting point, you could put something like the following
in a file `my-show.clj` and then invoke Afterglow as `java -jar afterglow.jar my-show.clj`:
```clojure
(ns my-show
"Set up the fixtures, effects, and cues I actually want to use."
;; TODO: Your list of required namespaces will differ from this, depending on
;; what fixtures you actually use, and what effects and cues you create.
(:require [afterglow.core :as core]
[afterglow.transform :as tf]
[afterglow.effects.color :refer [color-effect]]
[afterglow.effects.cues :as cues]
[afterglow.effects.dimmer :refer [dimmer-effect]]
[afterglow.effects.fun :as fun]
[afterglow.effects.movement :as move]
[afterglow.effects.oscillators :as oscillators]
[afterglow.effects.params :as params]
[afterglow.fixtures.blizzard :as blizzard]
[afterglow.rhythm :as rhythm]
[afterglow.show :as show]
[afterglow.show-context :refer :all]
[com.evocomputing.colors :refer [create-color hue adjust-hue]]
[taoensso.timbre :as timbre]))
(defonce ^{:doc "Holds my show if it has been created,
so it can be unregistered if it is being re-created."}
my-show
(atom nil))
(defn use-my-show
"Set up the show on the OLA universes it actually needs."
[]
;; Create, or re-create the show. Make it the default show so we don't
;; need to wrap everything below in a (with-show sample-show ...) binding.
(set-default-show!
(swap! my-show (fn [s]
(when s
(show/unregister-show s)
(with-show s (show/stop!)))
;; TODO: Edit this to list the actual OLA universe(s) that
;; your show needs to use if they are different than
;; just universe 1, as below, and change the description
;; to something descriptive and in your own style:
(show/show :universes [1] :description "My Show"))))
;; TODO: Replace this to patch in an actual fixture in your show, at its actual
;; universe, DMX address, physical location and orientation, then add all
;; your other fixtures one by one.
(show/patch-fixture! :torrent-1 (blizzard/torrent-f3) 1 1
:x (tf/inches 44) :y (tf/inches 51.75) :z (tf/inches -4.75)
:y-rotation (tf/degrees 0))
;; Return the show's symbol, rather than the actual map, which gets huge with
;; all the expanded, patched fixtures in it.
'*show*)
(core/init-logging) ; Log at :info level to rotating files in logs/ subdirectory.
(use-my-show) ; Set up my show as the default show, using the function above.
;; TODO: Add your custom effects, then assign them to cues with sensible colors
;; See afterglow.examples for examples.
```
As noted, you will want to look at the
[afterglow.examples](src/afterglow/examples.clj)
namespace for some examples of how to populate this file; the rest of
this section gives an overview and walk-through of how pieces of that
namespace work. The `:require` section at the top of `my-show.clj` is
set up to make it easy to cut and paste from these examples, although
it is not complete, and you will eventually need to learn how to
adjust and optimize it yourself.
> The example code above configures Afterglow to log to a set of
> rotating log files in a `logs/` subdirectory of your project.
> Afterglow will attempt to create that directory if it does not
> exist. If you want to see any logging information, which can be
> quite useful when troubleshooting, you will need to ensure that the
> path to the logs directory is writeable (or that the logs directory
> exists and is writable), otherwise the logging mechanism will
> silently do nothing. The logs will stay out of your way until you
> are interested in them, and take up a limited amount of space, but
> whenever you do want to watch what Afterglow is doing, you can look
> at them, or `tail -f logs/afterglow.log` to watch it live.
As your show gets more complex, you may want to split this into
multiple files, which you can either load by listing them all on the
command line, or by using Clojure’s `load-file` function from within
the first file. Or, once you are comfortable with idomatic Clojure
development, by organizing them into a hierarchy of namespaces, and
using the normal `:require` mechanism that is used to pull in
Afterglow’s own namespaces.
> :heavy_exclamation_mark: At this early stage of development, using
> Afterglow as an executable jar is less-tested territory, and you may
> find surprising bugs... though this is becoming less of an issue
> since the advent of
> [afterglow-max](https://github.com/Deep-Symmetry/afterglow-max#afterglow-max),
> which is putting Afterglow through its paces as an embedded jar. In
> any case, although the project will gradually evolve into a system
> that non-Clojure hackers can use, for now you are probably best off
> playing with it inside a Clojure development environment, or within
> Max, likely with a Clojure environment connected via nREPL.
Assuming you are using it from within a REPL, there is a namespace
`afterglow.examples` which is intended to help you get started quickly
in exploring the environment, as well as serving as an example of how
to configure your own shows, fixtures, effects, and cues.
> The next two lines are not needed if you are using a checkout of the
> Afterglow source code rather than the library version described
> above, since the project is configured to start you in this
> namespace for convenience.
```clojure
(require 'afterglow.examples)
(in-ns 'afterglow.examples)
```
When you run Afterglow as an executable jar, it will automatically
open a web browser window on its embedded web interface. If you are
using it in another way, you can bring up the web interface, and open
a browser window on it, with a one-liner like this (the first argument
specifies the port on which to run the web interface, and the second
controls whether a browser window should be automatically opened):
```clojure
(core/start-web-server 16000 true)
```
<img alt="Web Interface"
src="doc/modules/ROOT/assets/images/WebHome.png" width="537"
height="427">
As noted at the bottom, the web interface provides a minimal console
as well, so if you are running Afterglow from a jar and just want to
tweak something quickly, you can use that:
<img alt="Web Console"
src="doc/modules/ROOT/assets/images/Console.png" width="850"
height="690">
> However, this does not offer the valuable support you would have
> from a dedicated REPL like
> [Cider](https://github.com/clojure-emacs/cider) (in Emacs) or
> [Cursive](https://cursiveclojure.com) (in IntelliJ): things like
> symbol completion, popup documentation, and command-line recall,
> which make for a vastly more productive exploration session. So even
> when you are running from a jar rather than launching from a REPL,
> you will often want to access a real REPL. You can accomplish that
> with command-line arguments or by using the web console to invoke
> [core/start-nrepl](http://deepsymmetry.org/afterglow/api-doc/afterglow.core.html#var-start-nrepl)
> and then connecting your favorite REPL environment to the network
> REPL port you created.
The web interface does provide a nice show control page, though, with
access to a scrollable grid of cues, and the ability to track the cues
displayed on a physical cue grid control surface like the Ableton Push
or a current member of the Novation Launchpad family, so you can
control them from either place, and see the names that go with the
colored buttons on the control surface. This animated GIF shows how
cues respond to clicks, lightening while they run, and darkening any
cues which cannot run at the same time. It also shows how you can
scroll around a larger grid than fits on the screen at one time
(although it has reduced colors, frame rate, and quality when compared
to the actual web interface):
<img alt="Show Control"
src="doc/modules/ROOT/assets/images/ShowGrid.gif" width="998"
height="912">
Here is the Ableton Push interface tied to the same cue grid. This
physical control surface lets you trigger more than one cue at the
same time, and also gives you niceties unavailable with a mouse, like
pressure sensitivity so your effect intensity, speed, color, or other
parameters can be varied as you alter the pressure which you are
applying to the pads:
<img alt="Push Interface"
src="doc/modules/ROOT/assets/images/GrandMaster2.jpg" width="1200"
height="978">
You can adjust running effects, scroll around the cue grid, and adjust
or sync the show metronome from either interface. Other MIDI
controllers can be mapped to provide similar functionality, and
hopefully such mappings will make their way into Afterglow soon
(indeed, the current Novation Launchpad family is now supported too).
The Afterglow mappings are done entirely on the User layer as well, so
they coexist gracefully with Ableton Live, and you can switch back and
forth by pressing the User button if you want to perform with both.
But, getting back to our REPL-based example: We next start the sample
show, which runs on DMX universe 1. You will want to have OLA
configured to at least have an dummy universe with that ID so you can
watch the DMX values using its web interface. It would be even better
if you had an actual DMX interface hooked up, and changed the show to
include some real lights you have connected. Either way, here is how
you start the show sending control signals to lights:
```clojure
(use-sample-show) ; Create the sample show that uses universe 1.
(show/start!) ; Start sending its DMX frames.
```
The `afterglow.examples` namespace includes a helper function,
`fiat-lux`, to assign a nice cool blue color to all lights in the
sample show, set their dimmers to full, and open the shutters of the
Torrent moving-head spots, which can be called like this:
```clojure
(fiat-lux)
```
So if you happened to have the same fixtures hooked up, assigned the
same DMX addresses as I did when I wrote this, you would see a bunch
of blue light. More realistically, you can navigate to the `olad`
embedded [web server](http://localhost:9090/new/) and see the
non-zero DMX values in the blue and dimmer channels, assuming you have
set up a Universe with ID 1.
> In an environment where you are running multiple shows, the more
> general way of working with one would look like:
```clojure
(def another-show (some-function-that-creates-a-show))
(with-show another-show
(show/start!)
(fiat-lux))
```
> However, the `examples` namespace assumes you are just using one,
> and has set it up as the default show, like this:
```clojure
(set-default-show! sample-show)
```
> That saves us the trouble of wrapping all our show manipulation
> functions inside of `(with-show ...)` to establish a context. You
> will likely want to do something similar in setting up your own
> shows, since a single show is the most common scenario. See the
> `afterglow.show-context`
> [API documentation](http://deepsymmetry.org/afterglow/api-doc/afterglow.show-context.html)
> for more details. The `show-context` namespace also defines the
> dynamic variable `*show*` which you can use to refer to the current
> default show when you need to mention it explicitly, as you will see
> in some of the examples below.
The actual content of `fiat-lux` is quite simple, creating
three effects to achieve the goals mentioned above:
```clojure
(defn fiat-lux
"Start simple with a cool blue color from all the lights."
[]
(show/add-effect! :color (global-color-effect "slateblue"
:include-color-wheels? true))
(show/add-effect! :dimmers (global-dimmer-effect 255))
(show/add-effect! :torrent-shutter
(afterglow.effects.channel/function-effect
"Torrents Open" :shutter-open 50
(show/fixtures-named "torrent"))))
```
We can make the lights a little dimmer...
```clojure
(show/add-effect! :dimmers (global-dimmer-effect 200))
```
> Adding a function with the same keyword as an existing function
> replaces the old one. The dimmer channels drop from 255 to 200.
But for dimmer channels, there is an even better way of doing that:
```clojure
(master-set-level (:grand-master *show*) 80)
```
All cues which set dimmer levels are tied to a dimmer master chain.
If none is specified when creating the cue, they are tied directly
to the show’s dimmer grand master. Setting this to a value less than
100 scales the dimmer values sent to the lights down by that amount.
So the above command dims the lights to 80% of their possible
brightness, no matter what else the cues are trying to do. See the
[dimmer effects API documentation](http://deepsymmetry.org/afterglow/api-doc/afterglow.effects.dimmer.html)
for more details. Here is an example of what I call right away when
testing effects in my office with the little Korg nanoKONTROL 2
plugged in:
```clojure
(show/add-midi-control-to-master-mapping "slider" 0 7)
```
And then the last fader acts as my grand master dimmer, and I can
quickly get relief from overly bright lights. (In a real performance
context, you would want to use [this alternate
approach](https://afterglow-guide.deepsymmetry.org/afterglow/mapping_sync.html#automatic-bindings)
to automatically set up your bindings whenever the controller is
connected. That way, if someone trips over the controller cable, as
soon as you plug it back in, you are good to go again.)
> If you have an Ableton Push, it is even easier to have [intutive
> control](https://afterglow-guide.deepsymmetry.org/afterglow/push2.html#show-control)
> over your show’s grand master dimmer. As soon as you bind the Push
> to your show, the Push Master encoder is automatically tied to the
> show master dimmer, with nice graphical feedback in the text area.
> Plus you get deep control over the show metronome as well, as shown
> in the photo above. If you called `(use-sample-show)` as discussed
> above, as soon as you connect and power on your Push, Afterglow will
> activate its show control interface.
Moving on, though... we can change the global color to orange:
```clojure
(show/add-effect! :color (global-color-effect :orange))
```
> The color channel values change.
Let’s get a little fancy and ramp the dimmers up on a sawtooth curve each beat:
```clojure
(show/add-effect! :dimmers
(global-dimmer-effect (oscillators/build-oscillated-param
(oscillators/sawtooth))))
```
Slow that down a little:
```clojure
(afterglow.rhythm/metro-bpm (:metronome *show*) 70)
```
> If you have a web browser open on
> [your OLA daemon](http://localhost:9090/ola.html)’s DMX monitor for
> Universe 1, you will see the values for channels changing, then
> ramping up quickly, then a little more slowly after you change the
> BPM. OLA 0.9.5 introduced a new, beta web UI based on AngularJS
> which you can access through a small
> [New UI (Beta)](http://localhost:9090/new/) link at the bottom of
> the page. In my experience, it has been completely stable, looks a
> lot better, and is *far* more dynamic and responsive at monitoring
> changing DMX values, and presenting them in an intuitive at-a-glance
> way.
If you can, alter the example to use a universe and channels that you
will actually be able to see with a connected fixture, and watch
Clojure seize control of your lights!
### Further Experiments
If you have DJ software or a mixer sending you MIDI clock data, you
can sync the show’s BPM to it (see the [Developer
Guide](https://afterglow-guide.deepsymmetry.org/afterglow/mapping_sync.html#syncing-to-midi-clock)
for details, and for a Traktor controller mapping file that lets you
sync to its beat phase information as well):
```clojure
(show/sync-to-external-clock (afterglow.midi/sync-to-midi-clock "traktor"))
```
How about a nice cycling rainbow color fade?
```clojure
(def hue-param (oscillators/build-oscillated-param (oscillators/sawtooth :interval :bar)
:max 360))
(show/add-effect! :color (global-color-effect
(params/build-color-param :s 100 :l 50 :h hue-param)))
```
Or, if you need to be woken up a bit,
```clojure
(show/add-effect! :strobe (afterglow.effects.channel/function-cue
"Fast blast!" :strobe 100 (show/all-fixtures)))
```
> The [Developer Guide](https://afterglow-guide.deepsymmetry.org) has
> more examples of [building
> effects](https://afterglow-guide.deepsymmetry.org/afterglow/effects.html#effect-examples),
> and [mapping
> parameters](https://afterglow-guide.deepsymmetry.org/afterglow/mapping_sync.html)
> to MIDI controllers. There is also low-level [API
> documentation](http://deepsymmetry.org/afterglow/api-doc), but the
> project documentation is the best starting point for a conceptual
> overview and introduction.
When you are all done, you can terminate the effect handler thread...
```clojure
(show/stop!)
```
And darken the universe you were playing with.
```clojure
(show/blackout-show)
```
> An alternate way of accomplishing those last two steps would have
> been to call `(show/clear-effects!)` before `(show/stop!)` because
> once there were were no active effects, all the DMX values would
> settle back at zero and stay there until you stopped the show.
## Troubleshooting
When afterglow has important events to report, or encounters problems,
it writes log entries. In its default configuration, it tries to write
to a `logs` directory located in the current working directory from
which it was run. If that directory does not exist, and you have not
explicitly configured a path to a log file, it assumes you are not
interested in the logs, and silently suppresses them. So if things are
not going right, the first step is to enable logging. You can either
do this by creating a `logs` folder for Afterglow to use, or by
running it with the `-l` command-line argument to set an explicit log
file path, as described in the [Usage](#usage) section above. If you
do that, afterglow will create any missing directories in the log file
path, and fail with a clear error message if it is unable to log to
the place you asked it to.
The Open Lighting Architecture’s
[web interface](http://localhost:9090/new/#/), which you can find on
port 9090 of the machine running afterglow if you installed it in the
normal way, can be useful in troubleshooting as well. You can see if
the universes that afterglow is expecting to interact with actually
exist, are configured to talk to the lighting interfaces you expect,
and are sending DMX channel values that seem reasonable.
## Bugs
Although there are none known as of the time of this release, I am
sure some will be found, especially if you are tracking the master
branch to keep up with the current rapid pace of development. Please
feel free to log
[issues](https://github.com/Deep-Symmetry/afterglow/issues) as you
encounter them!
## What Next?
Everything beyond this point in this document is written for people
who are working on enhancing Afterglow itself.
If you are trying to learn how to use it, jump to the main [Developer
Guide](https://afterglow-guide.deepsymmetry.org) page now!
## Release Checklist
Here is the set of tasks needed to cut a new release:
### Prerelease Steps
- [ ] Check over the documentation. If any moving screen captures are
needed, see this [gist](https://gist.github.com/dergachev/4627207).
The command I have used so far is: `ffmpeg -i ~/Desktop/Cues.mov
-pix_fmt rgb24 -r 10 -f gif - | gifsicle --optimize=3 --delay=10 >
~/Desktop/Cues.gif`
- [ ] Update [`CHANGELOG.md`](CHANGELOG.md) to reflect the release:
make sure nothing is missing, and rename the sections to reflect the
fact that the unreleased code is now released, and there is nothing
unreleased.
- [ ] Tag the repository with a non -SNAPSHOT version.
### Release Steps
- [ ] Commit everything including the rebuilt API docs, tag the commit
with the tag name used above, and push including the tag. `git
commit -a`, `git tag -a v0.2.0 -m "Release 0.2.0"`, `git push --tags`.
- [ ] Deploy the release to Clojars: `lein deploy clojars`.
### Postrelease Steps
- [ ] Update [`CHANGELOG.md`](CHANGELOG.md) to include a new
unreleased section.
- [ ] Tag the repository with a new -SNAPSHOT version.
- [ ] Commit and push.
## Tasks
To a large extent, this is now historical, and issue and enhancement
tracking has moved to the
[issues](https://github.com/Deep-Symmetry/afterglow/issues) system. There
are still some interesting ideas here for longer-term consideration,
though.
- [x] Sync metronomes to MIDI
- [x] Add metronome chase for clear sync testing
- [x] Allow parameterized effects functions
- [x] Start wiki
- [x] Allow metronomes to be show variables
- [x] Improve Oscillators
- [x] Use keyword parameters
- [x] Add phrase oscillators
- [x] Finish wiki page
- [x] Migrate wiki documentation into project documentation.
- [x] Have metronome cue take metronome parameter and support dynamic
parameters.
- [x] Consider having patched fixture hold a reference to the show.
That way we could stop having to pass it so many places, though it
would make printing fixtures less useful. (Not needed; dynamic
binding works better.)
- [x] Add support for named fixture functions which exist as a value
range subset of a channel, and effects which set them to particular
values.
- [x] Allow scaling of named fixture functions, for example to allow a
strobe effect to be set to a rough Hz value despite differences in
fixture implementation.
- [x] Add color wheel support.
- [x] Review existing fixture definitions for consistency of function
names, start a style guide in the docs for others creating fixture
definitions.
- [x] Add configuration support for running OLA on a different machine.
- [ ] Make pass over all source, flesh out API doc and preconditions.
- [ ] Sparkle effect, essentially a particle generator with
configurable maximum brightness, fade time, distribution.
- [x] Get basic effect working until spatial features are available.
- [ ] Work both with arbitrary head list, and with spatially mapped origin/density.
- [ ] Work as single intensity, or spatially mapped hue/saturation patterns.
- [x] Implement a grand master dimmer in the show which imposes a
ceiling on all dimmer cues.
- [x] Also allow custom sub-master dimmer variables, chained off
each other and ultimately the grand master, assigned to cues. Each
step can scale the output.
- [x] All dimmer cues are assigned a master chain, defaulting to the
grand master if none supplied.
- [x] Consider implementing virtual dimmer effects to allow fixtures
which lack actual dimmer channels to simulate them and participate
in the dimmer master chain, as proposed
[here](https://github.com/Deep-Symmetry/afterglow/issues/43).
- [x] Get geometry engine and head-movement cues working.
- [x] Named cues: Define cues with a unique name so they can have
parameters saved for them, to be reloaded on future runs, once we
have a database. Also useful for compound cues, see below.
- [x] This requires cues to have a mechanism for reporting their
current variable values for saving, and to look them up when
saved.
- [ ] Compound cues:
- [x] Unflattened compound cues trigger multiple cues’ effects:
- [x] Each effect gets its own priority, parameters.
- [x] The compound finishes when all triggered effects do.
- [x] Implement by having the outer cue’s effect call
`show/add-effect-from-cue-grid!` to launch all the nested
effects, recording their IDs. The effect will never return any
assigners, but will report that it has ended when all of the
nested effects have ended. Telling this effect to end will, in
turn, call `show/end-effect!` on all nested cues (passing their
recorded id values as `:when-id`, to avoid inadvertently killing
later effects run under the same key).
- [x] Add a `:variable-overrides` parameter to
`show/add-effect-from-cue-grid!` so compound cues can use it to
customize the values of parameters introduced by nested cues.
- [ ] Flattened compound cues flatten their nested cues’ effects
into a single new effect:
- [ ] This effect gets assigned a new priority.
- [ ] The compound cue aggregates nested cue variables into one
big list, and passes them down to the nested effects. (Renaming
with numeric suffixes as needed to avoid name clashes? No, they
might be shared.)
- [ ] Compound cues created from solely named cues can be saved and
restored, so they can be built using the web (and rich controller)
interface out of existing cues, and current parameter values for
running cues.
- [ ] When creating a compound cue this way, will need to check
for and prevent circular definitions, as well as reporting
sensible errors when constituent cues can no longer be found.
- [x] Compound effects:
- [x] Simplest compound effect just delegates to nested effects,
returning concatenated assigners. But implement as a fade with
time zero?
- [x] Fade compound effect: fade in at start, out at end.
- [x] Cue list compound effect: Move through list of embedded
effects, with optional fades. Loops, driven by metronome, or a
variable parameter (knob controls where in the list we are). Maybe
different implementations?
- [x] Have effects pass a context map to children with show,
snapshot, own stuff? For example, so the children can be aware of
build, duration, a shared palette, other things? Or better, since
snapshot is already passed, just add a usually-nil section which
contains information about context, with at least information
about when overall effect started; current fade level of this
effect, fading in or out, when effect will end. Even better: This
can be assoc-ed on to the snapshot, without changing the
definition in rhythm.clj, since Clojure records are also maps!
_This seems to be unnecessary given how fades and chases ended up
being actually implemented._
- [x] Effects which can do their own blending implement an
additional interface. Otherwise the fade and chase effects (if
they are different), handle it. To fade between direction effects,
convert them to pan/tilt numbers, scale between those, then
convert back to a direction. _This was implemented as a
multimethod for each of the effect assigner types._
- [ ] Provide a mechanism for creating and controlling/monitoring
effects via OSC messages. Probably essentially a special-purpose OSC
REPL.
- [x] Add web page for viewing/adjusting cue variables; associate
metadata with the variables so the page can provide appropriate
editing tools and validation. Values live-update when controllers
change them.
- [ ] When it comes time to save data and settings, the luminus
approach looks good, including
[yesql](https://yobriefca.se/blog/2014/11/25/yesql-sql-in-sql-in-clojure/)
and h2 as the database. Need to figure out where it would be stored,
though.
- [x] See if I can get Traktor to just send beat notes for master
track; if so, add mode for MIDI sync to ride them like DJ link.
- [x] See example on page 166 of Traktor Bible; it is close, but I
want to add a condition that makes sure these pulses are sent only
for the deck which is currently the tempo master. Write to the
author for advice? Alternately, send separate messages when each
deck is set as the tempo master, and use those to keep track of
which beat pulses to pay attention to?
- [ ] See if I can detect which Pro DJ Link device is the current
master, and if so, add an option for down beat tracking using that.
- [x] Add tap tempo support for really low-end sync.
- [x] Add machine-readable metronome sync status flag, so Push can
color code it; detect stalled clocks even without stop signals.
- [x] See how Afterglow works as a hosted Max package, with an Inlet
to send Clojure code to be evaluated, and an outlet for the results.
This could be a quick way to add beat detection, sound spectrum
analysis, etc.
https://pcm.peabody.jhu.edu/~gwright/stdmp/docs/writingmaxexternalsinjava.pdf
https://docs.cycling74.com/max7/tutorials/jitterchapter51
https://docs.cycling74.com/max7/vignettes/packages
- [ ] Consider creating a
[Processing Library](https://github.com/processing/processing/wiki/Library-Overview)
for Afterglow. They are far more invested in the Java ecosystem than
Max is, anyway.
- [x] Provide a way to import QLC+ fixture definitions to help people
get started.
- [ ] Consider importing [Avolites](http://personalities.avolites.com)
personalities/fixture definitions (D4 files); they seem fairly
straightforward too.
- [x] Separate, or at least document clearly, how to use the low-level
OLA communication tools, for the benefit of people interested in
their own implementations.
- [x] Support Push version 2 if possible. As of March, 2016 this is
looking possible! Ableton has published
[detailed documentation](https://github.com/Ableton/push-interface)
of the MIDI and USB interfaces of the Push 2 and display.
- [x] For the display, I will need [Libusb](http://libusb.info/),
and there is a promising-looking Java wrapper,
[usb4Java](http://usb4java.org). Although there is a start at
[Clojure bindings](https://github.com/aamedina/tools.usb), it does
not seem to have gotten far.
- [x] Embed some [fonts](https://www.google.com/fonts#ReviewPlace:refine/Collection:Roboto|Open+Sans+Condensed:300|Source+Sans+Pro:400,200italic).
- [x] Support the Novation Launchpad series. The Pro has pressure
sensitivity, so start there. They also provide excellent programmer
documentation, so it will even be straightforward. For example,
[Launchpad Pro Programmers Reference Guide](http://global.novationmusic.com/sites/default/files/novation/downloads/10598/launchpad-pro-programmers-reference-guide_0.pdf),
found on their
[Downloads Page](http://global.novationmusic.com/support/product-downloads?product=Launchpad+Pro).
Having someone loan me one would speed this up!
- [x] Novation is loaning me a Launchpad Mini! It
[seems](http://www.soundonsound.com/sos/jan14/articles/novation-launchpad.htm)
to use the same control messages as the Launchpad S, so refer to
that guide in trying to set it up. Has only red/green 2 bit LEDs, so
don’t try to replicate colors from the cue grid.
- [x] Novation Launchpad Mk2 support added with the testing help of
[Benjamin Gudehus](https://github.com/hastebrot).
### Ideas
- [x] Model moving head location and position, so they can be panned and aimed in a coordinated way.
- [x] [Wikipedia](http://en.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions)
has the most promising overview of what I need to do.
- [ ] Use iOS device to help determine orientation of fixture: Hold
phone upright facing stage from audience perspective to set
reference attitude; move to match a landmark on the fixture
(documented in the fixture definition), and have phone use
[CoreMotion](https://developer.apple.com/library/ios/documentation/CoreMotion/Reference/CMAttitude_Class/index.html#//apple_ref/occ/instm/CMAttitude/multiplyByInverseOfAttitude:)
`CMAttitude` `multiplyByInverseOfAttitude` to determine the
difference.
- [x] The more I investigate, the more it looks like
[Java3D’s](http://docs.oracle.com/cd/E17802_01/j2se/javase/technologies/desktop/java3d/forDevelopers/J3D_1_3_API/j3dapi/)
[Transform3D](http://docs.oracle.com/cd/E17802_01/j2se/javase/technologies/desktop/java3d/forDevelopers/J3D_1_3_API/j3dapi/javax/media/j3d/Transform3D.html)
object is going to handle it for me, which is very convenient, as
it is already available in Clojure. To combine transformations,
just multiply them together (with the `mul` method).
- [x] Use `setEuler` to set a `Transform3D` to a specific set of
rotation angles.
- [x] If this leads to accuracy issues or loss of a degree of
freedom, consider Quaternions, as recommended in
this [article](http://java.sys-con.com/node/99792).
- [x] Wow, this may be exactly what I need: Java code for converting
Quaternions to Euler Angles:
http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/
The site is in general an amazing reference for the kind of geometry I need to learn.
- [x] This seems to be the formula I need to figure out the angles
to send a light to make it face a particular direction (the
selected, top answer):
http://stackoverflow.com/questions/1251828/calculate-rotations-to-look-at-a-3d-point
and transform.clj has an implementation in invert-direction. Now I
just need to test it with an actual light!
- [x] Remember that Vector3d has nice methods like angle (calculate
angle to another Vector3d), length, cross, dot...
- [ ] Render preview animations of a light show using WebGL.
- [x] This [shader](https://www.shadertoy.com/view/Mlj3W1) looks
nearly perfect, if I can figure out how to adopt it.
- [x] Looks like a nice intro to 3D, linear algebra, shaders:
[Making WebGL Dance](http://acko.net/files/fullfrontal/fullfrontal/webglmath/online.html).
Has interesting references too, such as
[Interactive 3D Graphics Course](https://www.udacity.com/course/interactive-3d-graphics--cs291),
Eric Haines, Udacity.com. And the
[Aerotwist tutorials](https://aerotwist.com/tutorials/), in
particular Three.js and shaders.
- [x] Fix the transform of lights into the WebGL shader space;
currently inconsistent.
- [ ] See if someone can come up with a more bare bones but scalable preview, probably building a geometry of the light cones instead of ray marching through them.
- [ ] Add a Focus effect type, which is resolved after direction and aim
effects are; this will allow, for example, fixtures to be annotated
with functions that map from focal distance to DMX value (the
Torrents would have two such functions, one for each gobo wheel),
and those functions could be used by an auto-focus effect which
would be given geometry information about the planes in the room
(floor, ceiling, walls, screens), could figure out the distance to
the nearest one the fixture is pointing at, and automatically
generate a focus channel value to focus at that distance. A fade
could be used with an oscillator to bounce back and forth between
focus on each gobo wheel.
- [x] Use [claypoole](https://clojars.org/com.climate/claypoole) for
parallelism.
- [ ] Change to core.async for all parallelism, since we are already using it anyway.
- [ ] Add OSC support (probably using
[Overtone’s implementation](https://github.com/rosejn/osc-clj))
for controller support, and MIDI as well.
- [x] Serious references for color manipulation, but in [Julia](https://github.com/timholy/Color.jl).
- [ ] Absolutely amazing reference on
[color vision](http://handprint.com/LS/CVS/color.html)! Send him a
note asking if he knows where I can find an algorithm for using
arbitrary LEDs to make an HSL color!
- [ ] Consider an alternate HSI color implementation. It could yield more
pure/accurate results, but perhaps with less intuitive semantics,
and definitely lower peak output. Most likely a configurable option?
See the discussion and code on the
[SaikoLED blog](http://blog.saikoled.com/post/44677718712/how-to-convert-from-hsi-to-rgb-white).
And [related discussion](http://blog.saikoled.com/post/43693602826/why-every-led-light-should-be-using-hsi),
with links to color correction.
- [ ] When it is time to optimize performance, study the
[type hints](http://clojure.org/java_interop#Java%20Interop-Type%20Hints)
interop information looks very informative and helpful.
- [ ] satisfies? seems to take twice as long as instance? so change the preconditions to use instance? where possible
- [ ] Do a pass through all files with *warn-on-reflection* set to
true, see what hinting can help. `(set! *warn-on-reflection*
true)` at top of file.
- [ ] Eventually create a leiningen task that can build a standalone
jar with Afterglow and a custom show definition file and its
supporting resources, so developers can easily deploy and share
shows with non-Clojurists.
- [ ] Consider adding support for metronome synchronization with
[EspGrid](https://github.com/d0kt0r0/EspGrid).
- [ ] Investigate whether
[Vamp](https://code.soundsoftware.ac.uk/projects/vamp) (and jVamp)
would be worthwhile for audio analysis. But adding native components
is likely to be a hassle.
- [ ] See if [thi-ng/color](https://github.com/thi-ng/color) is a
better fit.
- [x] Once I release the first version, answer this StackOverflow
[question](http://stackoverflow.com/questions/9582192/dmx-software-to-control-lights-with-programmable-interface).
- [x] Also submit a link to
[TOPLAP](http://toplap.org/contact-page/).
- [ ] Also post a followup to this
[article](http://radar.oreilly.com/2015/05/creative-computing-with-clojure.html)
and reach out to some of the artists themselves. (Sadly too late
to post a comment on the thread, but I should try contacting the
artists!)
### References
* Clojure implementation of Protocol Buffers via
[lein-protobuf](https://github.com/flatland/lein-protobuf) and
[clojure-protobuf](https://github.com/flatland/clojure-protobuf).
* The incomplete
[Java OLA client](https://github.com/OpenLightingProject/ola/tree/master/java).
* Making [Animated GIF Screencasts](https://gist.github.com/dergachev/4627207).
### Related Work
- [x] Rich controller support for
[Ableton Push](https://forum.ableton.com/viewtopic.php?f=55&t=193744)!
- [x] [Color chart](https://forum.ableton.com/viewtopic.php?f=55&t=192920),
post a followup if my hue theory pans out.
- [x] Nice
[breakdown](http://tai-studio.org/index.php/projects/sound-programming/accessing-abletons-push-device/)
of button sections.
- [x] It looks like I can actually specify an RGB color for the buttons using another SysEx:
[PushPix](https://cycling74.com/wiki/index.php?title=Push_Programming_Oct13_03)
- [ ] Could add basic grid control using the
[Livid Ohm RGB](http://wiki.lividinstruments.com/wiki/OhmRGB),
alhough it supports only 7 colors, no pressure sensitivity, and has
been discontinued, so this is a low priority even though we own one,
unless/until someone requests it.
- [x] Add a user interface using [Luminus](http://www.luminusweb.net/docs).
- [x] Separate [ola-clojure](https://github.com/Deep-Symmetry/ola-clojure#ola-clojure) into its own project.
## License
<a href="http://deepsymmetry.org"><img align="right" alt="Deep Symmetry"
src="doc/modules/ROOT/assets/images/DS-logo-github.png" width="250" height="150"></a>
Copyright © 2015-2023 [Deep Symmetry, LLC](http://deepsymmetry.org)
Distributed under the [Eclipse Public License
2.0](https://opensource.org/licenses/EPL-2.0). By using this software
in any fashion, you are agreeing to be bound by the terms of this
license. You must not remove this notice, or any other, from this
software. A copy of the license can be found in
[LICENSE](https://github.com/Deep-Symmetry/afterglow/blob/master/LICENSE)
within this project.
<a href="https://www.netlify.com">
<img align="right" src="https://www.netlify.com/img/global/badges/netlify-color-accent.svg"/>
</a>
### [Antora](https://antora.org)
Antora is used to build the [Developer
Guide](https://afterglow-guide.deepsymmetry.org), for embedding inside
the application, and hosting on [Netlify](https://www.netlify.com).
Antora is licensed under the [Mozilla Public License Version
2.0](https://www.mozilla.org/en-US/MPL/2.0/) (MPL-2.0).
================================================
FILE: doc/README.md
================================================
# Developer Guide Module
> :mag_right: If you are looking for the online documentation, it has
> [moved](https://afterglow-guide.deepsymmetry.org/) off of
> GitHub to become easier to read and navigate.
Afterglow now uses [Antora](https://antora.org) to build its Developer
Guide. this folder hosts the documentation module and playbooks used
to build it. `embedded.yml` is used to create the self-hosted version
which is served out of Afterglow itself, so it can be used even
without an Internet connection, and `github-actions.yml` is used to build the
[online version](https://afterglow-guide.deepsymmetry.org/) that is
built by GitHub Actions.
The Leiningen project in the root of this repository automatically
invokes Antora to build the embedded version as an early build step.
The online version, which will grow to support multiple released
versions of Afterglow, is built automatically whenever changes are
pushed to the relevant branches on GitHub.
================================================
FILE: doc/antora.yml
================================================
name: afterglow
title: Afterglow
version: ~
display_version: 'main'
prerelease: true
start_page: ROOT:README.adoc
asciidoc:
attributes:
icons: font
experimental: ''
page-copyright: 2015–2022
page-pagination: ''
nav:
- modules/ROOT/nav.adoc
================================================
FILE: doc/ds.yml
================================================
site:
title: Afterglow Developer Guide
url: https://deepsymmetry.org/afterglow/guide
start_page: afterglow::README.adoc
content:
sources:
- url: https://github.com/Deep-Symmetry/afterglow.git
branches: [main, guide*]
tags: va*
start_path: doc
antora:
extensions:
- require: '@antora/lunr-extension'
index_latest_only: true
asciidoc:
attributes:
api-doc: http://deepsymmetry.org/afterglow/api-doc/
icons: font
experimental: ''
ui:
bundle:
url: https://deepsymmetry.org/media/antora/ui-bundle-3.zip
snapshot: true
================================================
FILE: doc/embedded.yml
================================================
site:
title: Afterglow Developer Guide
url: http:/guide
start_page: afterglow::README.adoc
content:
edit_url: false
sources:
- url: ./..
branches: HEAD
start_path: doc
antora:
extensions:
- '@antora/lunr-extension'
asciidoc:
attributes:
api-doc: link:../../api-doc/
icons: font
experimental: ''
ui:
bundle:
url: https://deepsymmetry.org/media/antora/ui-bundle-3-self.zip
snapshot: true
output:
dir: ./../target/classes/developer_guide
================================================
FILE: doc/github-actions.yml
================================================
site:
title: Afterglow Developer Guide
url: https://afterglow-guide.deepsymmetry.org/
start_page: afterglow::README.adoc
robots: allow
keys:
google_analytics: G-ZNLRY347Y7
urls:
redirect_facility: httpd
content:
sources:
- url: https://github.com/Deep-Symmetry/afterglow.git
branches: [main, guide*]
start_path: doc
antora:
extensions:
- require: '@antora/lunr-extension'
index_latest_only: true
asciidoc:
attributes:
api-doc: https://afterglow-guide.deepsymmetry.org/api/
icons: font
experimental: ''
ui:
bundle:
url: https://deepsymmetry.org/media/antora/ui-bundle-3-self.zip
snapshot: true
================================================
FILE: doc/modules/ROOT/assets/source/Afterglow logo.ai
================================================
%PDF-1.5
%
1 0 obj
<</Metadata 2 0 R/OCProperties<</D<</ON[5 0 R 31 0 R 81 0 R 131 0 R]/Order 132 0 R/RBGroups[]>>/OCGs[5 0 R 31 0 R 81 0 R 131 0 R]>>/Pages 3 0 R/Type/Catalog>>
endobj
2 0 obj
<</Length 50109/Subtype/XML/Type/Metadata>>stream
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c067 79.157747, 2015/03/30-23:40:42 ">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
xmlns:xmpGImg="http://ns.adobe.com/xap/1.0/g/img/"
xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
xmlns:stMfs="http://ns.adobe.com/xap/1.0/sType/ManifestItem#"
xmlns:illustrator="http://ns.adobe.com/illustrator/1.0/"
xmlns:xmpTPg="http://ns.adobe.com/xap/1.0/t/pg/"
xmlns:stDim="http://ns.adobe.com/xap/1.0/sType/Dimensions#"
xmlns:xmpG="http://ns.adobe.com/xap/1.0/g/"
xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
<dc:format>application/pdf</dc:format>
<dc:title>
<rdf:Alt>
<rdf:li xml:lang="x-default">OLA Logo RGB CS6</rdf:li>
</rdf:Alt>
</dc:title>
<xmp:MetadataDate>2015-07-20T14:20:46-05:00</xmp:MetadataDate>
<xmp:ModifyDate>2015-07-20T14:20:46-05:00</xmp:ModifyDate>
<xmp:CreateDate>2015-07-20T12:06:36-05:00</xmp:CreateDate>
<xmp:CreatorTool>Adobe Illustrator CC 2015 (Macintosh)</xmp:CreatorTool>
<xmp:Thumbnails>
<rdf:Alt>
<rdf:li rdf:parseType="Resource">
<xmpGImg:width>256</xmpGImg:width>
<xmpGImg:height>136</xmpGImg:height>
<xmpGImg:format>JPEG</xmpGImg:format>
<xmpGImg:image>/9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA
AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK
DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f
Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAiAEAAwER
AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA
AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB
UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE
1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ
qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy
obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp
0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo
+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FVk88EELzT
yLFDGC0krkKqqOpLHYDFXknnL/nKf8pfLbvBBfSa9eJUGLS1EsYPvOzJCR/qM3yxV5Fr3/ObvmKV
2XQPLdpaJ0V76WS5Y+5WL6uB8qnFWHXn/OXP5zzvyivbO0H8kNpGR/yV9U/jiqEj/wCcrPzwVnLa
5E4Y1VWsrOi+w4xA/fXFU203/nMb83LVl+sppmoKKBhPbMhNOprBJFucVZ/5b/5zesndI/MvlqSF
f27rTphL/wAkJhH/AMncVe1eR/zq/LXzqUi0TWoTfv0025/0e6r4LHJTn/sOQxVnGKuxV2KuxV2K
uxVLPM3/ABwb3/jGf1jJQ5uVov76PveUXH90cyC9ZDmxfVP2sw8rtcDFb3+9HzzUah2+LkmOnds1
OVxszKtN7ZrcrqszKdN7ZrsrqszIF/uRmH1dXlSvUv7psysPN1uo5PPte/azpNG8lrmIf7ub551O
jecyJhbds6zROJNf+1mVld92ejYvsDMGXN6vF9L6cznnJdirsVeV/nB/zkL5R/LuN7Ef7lvMrLWP
SoWAEdRUNcyb+mKbgULHwpvir41/MT84vPvn65Ztd1FhYcuUOlW5MVpHQ1H7oE8yP5nJb3xVhOKt
gEmg3JxVMbby35hugGttMupVPRkhkK7+4FMujp8kuUT8mByRHMhE/wCCvN3/AFZ7v/kU39Ml+Uy/
zT8mPjQ7wgLvRtYsxW8sbi2A7zROnv8AtAZXLFOPMEfBmJxPIoPK2TYJBBBoRuCOtcVe1flb/wA5
TeefKLw2OuO/mLQVopiuHJu4l6fubhqk0/lkqOwK4q+xvIn5heVPPWirq/ly8W5g2W4gb4Z4HIrw
mj6q34HsSMVZHirsVdirsVSzzN/xwb3/AIxn9YyUOblaL++j73lFx/dHMgvWQ5sX1T9rMPK7XAxW
9/vR881Godvi5Jjp3bNTlcbMyrTe2a3K6rMynTe2a7K6rMyBf7kZh9XV5Ur1L+6bMrDzdbqOTz7X
v2s6TRvJa5iH+7m+edTo3nMiYW3bOs0TiTX/ALWZWV33Z6Ni+wMwZc3q8X0vpzOecl2Kvnn/AJyK
/wCckF8rev5T8oTq/mMjjf6ivF0sgesaVqGn8aiif63RV8a3FxcXM8lxcyvNcTMXlmkYu7uxqzMx
qSSepOKq+maTqOqXa2mn273Nw37CCtB4seij3OWY8UpmoiyxlMRFl6f5d/JeFQs+vXBkfr9UtzRR
7PIRU/7Gnzzc4OyRzyH4Bwcmt/ms/wBL8uaDpShdPsIbcj/diqC/0uaufpObTHp4Q+kAOHLJKXMp
llzB2KuxVItY8j+VtWVvrWnxrK3+74R6UlfHklK/7KuYuXR4p8w2wzzjyLzXzP8Ak/qdir3OjSG/
t13NuwAuAPanwv8ARQ+2ajUdlSjvD1D7XOxawHaWzz10dHZHUq6khlIoQR1BGakhzE98keevMvkr
Xodb8v3ZtruP4ZENTFNHUFopkqOaNTp9IoaHFX3t+T35w+X/AMyvL4vLMi11i1CrqulM1Xhc/tp/
NE37LfQd8VZ/irsVdiqWeZv+ODe/8Yz+sZKHNytF/fR97yi4/ujmQXrIc2L6p+1mHldrgYre/wB6
Pnmo1Dt8XJMdO7ZqcrjZmVab2zW5XVZmU6b2zXZXVZmQL/cjMPq6vKlepf3TZlYebrdRyefa9+1n
SaN5LXMQ/wB3N886nRvOZEwtu2dZonEmv/azKyu+7PRsX2BmDLm9Xi+l9OZzzkvFv+clPzt/wFoK
6LosoHmrVo29BxQm0tz8LXBH85NRH71PahVfC0kkksjSSMXkclndiSxYmpJJ6k4qn3k/ydqHmXUP
RhrFaREG6uyKqgPYeLHsMytLpZZpUOXUtObMIDze8aB5d0nQrIWmnQiNf92SHeSRv5nbvnT4MEMU
aiHU5MhmbKZ5cwdiq2SSONGkkYJGgLO7GgAHUknATSpXZebPLV7dfVLTU7ea4JosayCrHwX+b6Mp
hqccjQkLbJYpAWQm2XtbsVdirEPPH5eaf5hha5twttqyj4JwKLJQbLLT/iXUZgazQxyixtL8c3Iw
agw2PJ4Xf2F5YXktneRNDcwtxkjbqD/TwOczOBiaOxDtoyBFhN/IvnfXvJXmW08waJN6d3bGjxtX
05oiRzhlUEckem/3jcA5FL9Evy9896L568qWfmPSG/cXK8Z7diC8E6/3kL0/aU/eKHocVZHirsVS
zzN/xwb3/jGf1jJQ5uVov76PveUXH90cyC9ZDmxfVP2sw8rtcDFb3+9HzzUah2+LkmOnds1OVxsz
KtN7ZrcrqszKdN7ZrsrqszIF/uRmH1dXlSvUv7psysPN1uo5PPte/azpNG8lrmIf7ub551OjecyJ
hbds6zROJNf+1mVld92ejYvsDMGXN6vF9L6I80+ZNM8s+XdR1/VH4WOmwPPMRTkeI2Ra0qztRVHi
c55yX5t+d/N+recPNOo+Y9Vfld6hKZOANVjjG0cSf5MaAKPliqt5A8kax5382WHlzSl/0i8f95OR
VIYV3kmf/JRd/c7dTir9CPJH5ZeTfJmj2mm6Np8QNqATeSqHuJJKAPK0hFQzkVPGg8ABlniy4eG/
T3MeAXdbsqytkgNT0HRdUUjULGG5JUoHkRS4B68X+0v0HLceacPpJDCWOMuYYTc/kpokmo+tDezw
WBqXtAFZwSTskrdFG32lY++bGPa8xGiAZd/7HFOijfPZ41/zlt5bsfLHk/QoNHt5I4b66kj1C8aR
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</xmpGImg:image>
</rdf:li>
</rdf:Alt>
</xmp:Thumbnails>
<xmpMM:InstanceID>uuid:179f7641-5b82-f94b-aee1-41c2d263a77c</xmpMM:InstanceID>
<xmpMM:DocumentID>xmp.did:3f5bc854-b7f3-4530-a63b-4e8e84ed7795</xmpMM:DocumentID>
<xmpMM:OriginalDocumentID>uuid:5D20892493BFDB11914A8590D31508C8</xmpMM:OriginalDocumentID>
<xmpMM:RenditionClass>proof:pdf</xmpMM:RenditionClass>
<xmpMM:DerivedFrom rdf:parseType="Resource">
<stRef:instanceID>uuid:4b8d80fb-79e0-e648-b7e5-561a697a3c5f</stRef:instanceID>
<stRef:documentID>xmp.did:78407A0809206811822AB037A4F90ED9</stRef:documentID>
<stRef:originalDocumentID>uuid:5D20892493BFDB11914A8590D31508C8</stRef:originalDocumentID>
<stRef:renditionClass>proof:pdf</stRef:renditionClass>
</xmpMM:DerivedFrom>
<xmpMM:History>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<stEvt:action>saved</stEvt:action>
<stEvt:instanceID>xmp.iid:0180117407206811822AB037A4F90ED9</stEvt:instanceID>
<stEvt:when>2012-08-26T15:46:21+10:00</stEvt:when>
<stEvt:softwareAgent>Adobe Illustrator CS6 (Macintosh)</stEvt:softwareAgent>
<stEvt:changed>/</stEvt:changed>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<stEvt:action>saved</stEvt:action>
<stEvt:instanceID>xmp.iid:3f5bc854-b7f3-4530-a63b-4e8e84ed7795</stEvt:instanceID>
<stEvt:when>2015-07-20T12:06:36-05:00</stEvt:when>
<stEvt:softwareAgent>Adobe Illustrator CC 2015 (Macintosh)</stEvt:softwareAgent>
<stEvt:changed>/</stEvt:changed>
</rdf:li>
</rdf:Seq>
</xmpMM:History>
<xmpMM:Manifest>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<stMfs:linkForm>EmbedByReference</stMfs:linkForm>
<stMfs:reference rdf:parseType="Resource">
<stRef:filePath>/Users/jim/Desktop/Beam.png</stRef:filePath>
<stRef:documentID>0</stRef:documentID>
<stRef:instanceID>0</stRef:instanceID>
</stMfs:reference>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<stMfs:linkForm>EmbedByReference</stMfs:linkForm>
<stMfs:reference rdf:parseType="Resource">
<stRef:filePath>/Users/jim/Desktop/Clojure Beam.png</stRef:filePath>
<stRef:documentID>0</stRef:documentID>
<stRef:instanceID>0</stRef:instanceID>
</stMfs:reference>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<stMfs:linkForm>EmbedByReference</stMfs:linkForm>
<stMfs:reference rdf:parseType="Resource">
<stRef:filePath>/Users/jim/Desktop/Clojure-Logo.png</stRef:filePath>
<stRef:documentID>0</stRef:documentID>
<stRef:instanceID>0</stRef:instanceID>
</stMfs:reference>
</rdf:li>
</rdf:Seq>
</xmpMM:Manifest>
<xmpMM:Ingredients>
<rdf:Bag>
<rdf:li rdf:parseType="Resource">
<stRef:filePath>/Users/jim/Desktop/Beam.png</stRef:filePath>
<stRef:documentID>0</stRef:documentID>
<stRef:instanceID>0</stRef:instanceID>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<stRef:filePath>/Users/jim/Desktop/Clojure Beam.png</stRef:filePath>
<stRef:documentID>0</stRef:documentID>
<stRef:instanceID>0</stRef:instanceID>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<stRef:filePath>/Users/jim/Desktop/Clojure-Logo.png</stRef:filePath>
<stRef:documentID>0</stRef:documentID>
<stRef:instanceID>0</stRef:instanceID>
</rdf:li>
</rdf:Bag>
</xmpMM:Ingredients>
<illustrator:Type>Document</illustrator:Type>
<illustrator:StartupProfile>Print</illustrator:StartupProfile>
<xmpTPg:HasVisibleOverprint>False</xmpTPg:HasVisibleOverprint>
<xmpTPg:HasVisibleTransparency>True</xmpTPg:HasVisibleTransparency>
<xmpTPg:NPages>1</xmpTPg:NPages>
<xmpTPg:MaxPageSize rdf:parseType="Resource">
<stDim:w>296.999935</stDim:w>
<stDim:h>210.001661</stDim:h>
<stDim:unit>Millimeters</stDim:unit>
</xmpTPg:MaxPageSize>
<xmpTPg:PlateNames>
<rdf:Seq>
<rdf:li>Cyan</rdf:li>
<rdf:li>Magenta</rdf:li>
<rdf:li>Yellow</rdf:li>
<rdf:li>Black</rdf:li>
</rdf:Seq>
</xmpTPg:PlateNames>
<xmpTPg:SwatchGroups>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<xmpG:groupName>Default Swatch Group</xmpG:groupName>
<xmpG:groupType>0</xmpG:groupType>
<xmpG:Colorants>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>White</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>255</xmpG:red>
<xmpG:green>255</xmpG:green>
<xmpG:blue>255</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>Black</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>29</xmpG:red>
<xmpG:green>29</xmpG:green>
<xmpG:blue>27</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>CMYK Red</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>226</xmpG:red>
<xmpG:green>6</xmpG:green>
<xmpG:blue>19</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>CMYK Yellow</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>255</xmpG:red>
<xmpG:green>236</xmpG:green>
<xmpG:blue>0</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>CMYK Green</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>0</xmpG:red>
<xmpG:green>149</xmpG:green>
<xmpG:blue>64</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>CMYK Cyan</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>0</xmpG:red>
<xmpG:green>158</xmpG:green>
<xmpG:blue>226</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>CMYK Blue</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>49</xmpG:red>
<xmpG:green>39</xmpG:green>
<xmpG:blue>130</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>CMYK Magenta</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>229</xmpG:red>
<xmpG:green>0</xmpG:green>
<xmpG:blue>126</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=15 M=100 Y=90 K=10</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>189</xmpG:red>
<xmpG:green>22</xmpG:green>
<xmpG:blue>34</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=90 Y=85 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>229</xmpG:red>
<xmpG:green>51</xmpG:green>
<xmpG:blue>42</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=80 Y=95 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>232</xmpG:red>
<xmpG:green>78</xmpG:green>
<xmpG:blue>27</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=50 Y=100 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>242</xmpG:red>
<xmpG:green>145</xmpG:green>
<xmpG:blue>0</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=35 Y=85 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>248</xmpG:red>
<xmpG:green>177</xmpG:green>
<xmpG:blue>51</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=5 M=0 Y=90 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>252</xmpG:red>
<xmpG:green>234</xmpG:green>
<xmpG:blue>13</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=20 M=0 Y=100 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>221</xmpG:red>
<xmpG:green>219</xmpG:green>
<xmpG:blue>0</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=50 M=0 Y=100 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>147</xmpG:red>
<xmpG:green>192</xmpG:green>
<xmpG:blue>31</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=75 M=0 Y=100 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>57</xmpG:red>
<xmpG:green>169</xmpG:green>
<xmpG:blue>53</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=85 M=10 Y=100 K=10</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>0</xmpG:red>
<xmpG:green>141</xmpG:green>
<xmpG:blue>54</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=90 M=30 Y=95 K=30</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>0</xmpG:red>
<xmpG:green>102</xmpG:green>
<xmpG:blue>51</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=75 M=0 Y=75 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>45</xmpG:red>
<xmpG:green>171</xmpG:green>
<xmpG:blue>102</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=80 M=10 Y=45 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>0</xmpG:red>
<xmpG:green>160</xmpG:green>
<xmpG:blue>153</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=70 M=15 Y=0 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>53</xmpG:red>
<xmpG:green>168</xmpG:green>
<xmpG:blue>224</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=85 M=50 Y=0 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>29</xmpG:red>
<xmpG:green>112</xmpG:green>
<xmpG:blue>183</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=100 M=95 Y=5 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>45</xmpG:red>
<xmpG:green>46</xmpG:green>
<xmpG:blue>130</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=100 M=100 Y=25 K=25</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>41</xmpG:red>
<xmpG:green>35</xmpG:green>
<xmpG:blue>92</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=75 M=100 Y=0 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>102</xmpG:red>
<xmpG:green>36</xmpG:green>
<xmpG:blue>130</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=50 M=100 Y=0 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>148</xmpG:red>
<xmpG:green>27</xmpG:green>
<xmpG:blue>128</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=35 M=100 Y=35 K=10</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>162</xmpG:red>
<xmpG:green>25</xmpG:green>
<xmpG:blue>91</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=10 M=100 Y=50 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>214</xmpG:red>
<xmpG:green>11</xmpG:green>
<xmpG:blue>81</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=95 Y=20 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>230</xmpG:red>
<xmpG:green>27</xmpG:green>
<xmpG:blue>114</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=25 M=25 Y=40 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>202</xmpG:red>
<xmpG:green>186</xmpG:green>
<xmpG:blue>159</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=40 M=45 Y=50 K=5</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>163</xmpG:red>
<xmpG:green>137</xmpG:green>
<xmpG:blue>122</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=50 M=50 Y=60 K=25</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>122</xmpG:red>
<xmpG:green>106</xmpG:green>
<xmpG:blue>88</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=55 M=60 Y=65 K=40</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>99</xmpG:red>
<xmpG:green>78</xmpG:green>
<xmpG:blue>66</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=25 M=40 Y=65 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>201</xmpG:red>
<xmpG:green>157</xmpG:green>
<xmpG:blue>102</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=30 M=50 Y=75 K=10</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>177</xmpG:red>
<xmpG:green>127</xmpG:green>
<xmpG:blue>73</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=35 M=60 Y=80 K=25</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>146</xmpG:red>
<xmpG:green>95</xmpG:green>
<xmpG:blue>54</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=40 M=65 Y=90 K=35</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>126</xmpG:red>
<xmpG:green>78</xmpG:green>
<xmpG:blue>36</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=40 M=70 Y=100 K=50</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>104</xmpG:red>
<xmpG:green>59</xmpG:green>
<xmpG:blue>17</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=50 M=70 Y=80 K=70</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>66</xmpG:red>
<xmpG:green>41</xmpG:green>
<xmpG:blue>24</xmpG:blue>
</rdf:li>
</rdf:Seq>
</xmpG:Colorants>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:groupName>Grays</xmpG:groupName>
<xmpG:groupType>1</xmpG:groupType>
<xmpG:Colorants>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=0 Y=0 K=100</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>29</xmpG:red>
<xmpG:green>29</xmpG:green>
<xmpG:blue>27</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=0 Y=0 K=90</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>60</xmpG:red>
<xmpG:green>60</xmpG:green>
<xmpG:blue>59</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=0 Y=0 K=80</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>87</xmpG:red>
<xmpG:green>87</xmpG:green>
<xmpG:blue>86</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=0 Y=0 K=70</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>111</xmpG:red>
<xmpG:green>111</xmpG:green>
<xmpG:blue>110</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=0 Y=0 K=60</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>134</xmpG:red>
<xmpG:green>134</xmpG:green>
<xmpG:blue>134</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=0 Y=0 K=50</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>156</xmpG:red>
<xmpG:green>155</xmpG:green>
<xmpG:blue>155</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=0 Y=0 K=40</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>177</xmpG:red>
<xmpG:green>177</xmpG:green>
<xmpG:blue>177</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=0 Y=0 K=30</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>198</xmpG:red>
<xmpG:green>198</xmpG:green>
<xmpG:blue>197</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=0 Y=0 K=20</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>217</xmpG:red>
<xmpG:green>217</xmpG:green>
<xmpG:blue>217</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=0 Y=0 K=10</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>236</xmpG:red>
<xmpG:green>236</xmpG:green>
<xmpG:blue>236</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=0 Y=0 K=5</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>245</xmpG:red>
<xmpG:green>245</xmpG:green>
<xmpG:blue>245</xmpG:blue>
</rdf:li>
</rdf:Seq>
</xmpG:Colorants>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:groupName>Brights</xmpG:groupName>
<xmpG:groupType>1</xmpG:groupType>
<xmpG:Colorants>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=100 Y=100 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>226</xmpG:red>
<xmpG:green>6</xmpG:green>
<xmpG:blue>19</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=75 Y=100 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>233</xmpG:red>
<xmpG:green>90</xmpG:green>
<xmpG:blue>12</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=0 M=10 Y=95 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>255</xmpG:red>
<xmpG:green>221</xmpG:green>
<xmpG:blue>0</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=85 M=10 Y=100 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>0</xmpG:red>
<xmpG:green>151</xmpG:green>
<xmpG:blue>58</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=100 M=90 Y=0 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>40</xmpG:red>
<xmpG:green>52</xmpG:green>
<xmpG:blue>138</xmpG:blue>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<xmpG:swatchName>C=60 M=90 Y=0 K=0</xmpG:swatchName>
<xmpG:mode>RGB</xmpG:mode>
<xmpG:type>PROCESS</xmpG:type>
<xmpG:red>129</xmpG:red>
<xmpG:green>53</xmpG:green>
<xmpG:blue>138</xmpG:blue>
</rdf:li>
</rdf:Seq>
</xmpG:Colorants>
</rdf:li>
</rdf:Seq>
</xmpTPg:SwatchGroups>
<pdf:Producer>Adobe PDF library 10.01</pdf:Producer>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
endstream
endobj
3 0 obj
<</Count 1/Kids[7 0 R]/Type/Pages>>
endobj
7 0 obj
<</ArtBox[78.7725 155.908 623.663 444.145]/BleedBox[0.0 0.0 841.89 595.28]/Contents 133 0 R/CropBox[0.0 0.0 841.89 595.28]/Group 134 0 R/LastModified(D:20150720142046-05'00')/MediaBox[0.0 0.0 841.89 595.28]/Parent 3 0 R/PieceInfo<</Illustrator 135 0 R>>/Resources<</ColorSpace<</CS0 136 0 R>>/ExtGState<</GS0 137 0 R/GS1 138 0 R>>/ProcSet[/PDF/ImageC]/Properties<</MC0 131 0 R>>/Shading<</Sh0 139 0 R>>/XObject<</Im0 140 0 R/Im1 141 0 R>>>>/Thumb 142 0 R/TrimBox[0.0 0.0 841.89 595.28]/Type/Page>>
endobj
133 0 obj
<</Filter/FlateDecode/Length 957>>stream
H|VɎ1W*;+Bq{2a_9K"C~:}zLJHo>)ʉ.5ҟo3#xnUF_JPլĵhOSGmYmւTnĒ%H 89X}n/v$z9:<?E&ZMRᠵVw[OPZ&my qp4THFq7$Ki͵0<Ny_+"j~O4TeoSX4+b'fkru_y#mNйM8b{>Pέ'%ugJ@r@/3ZME/))5WySoTx_
FME]0~U4l}9&G -HHa>`b*hah<ZP
c+LuI*ɲ8Y1.K?&ܩvNFx
@IWMa)E.8V{{3/1b藂)e`ANPĒ=|:$Hٕ6A8S"#Pݵ |5Գ\{S. F9:Hn>zsN5CCwdM.2kNUwiݫ)!b~)>Ez6f^T7NH4m;
7QI#X(pWe:DÖ2&}T$ [q[|Qdk02
ZEQEDkz"ϱ'p%F=1<!Ī.3<dϤjjin
;`$G
n
hCD-jSs pV(hj _UrOQ;xaB:sJد5{8!Ǹ_=oH/?Wz}W j
endstream
endobj
134 0 obj
<</CS 143 0 R/I false/K false/S/Transparency>>
endobj
142 0 obj
<</BitsPerComponent 8/ColorSpace 144 0 R/Filter[/ASCII85Decode/FlateDecode]/Height 74/Length 868/Width 105>>stream
8;Z]!9krEJ'Z]AHr]#]?fMWl8:D(FY#cU>X/k[,i#/&&Mfsk0%$7bELP`;2e69sDW
oNeBdV:!?r'ROA(@@f31,;[+4o\Nq2!3TS;EokH/X+-f!=?X>[(KjmAeY,1ha!@3/
`np??:[ZM8%dA9EH=EDbkDYP3eZQ(@4NEAB2t<g,DKR\1LW,6Z\C'.e\fEH%TDV\s
KfL;JUHLE5PgT5`N-[0`PVd\;MK8Steb-254^(U/Irtt:=Smj.O<0SMd6f@0PlJl>
Hoi7=C"+[Q"sHk4;c7C1^@E=NA7W1eY:f),S".'1bDc50'1T&nrY\1G`IJ)"kM79j
OX%En%fi@#dreP2q]'a*+[;o#)4(QKdE*B/:PP'AA<S82?fMF8,q2/rq@gdUP!G5t
F!R'gJqHl,&5kh^.fS-IW\q*(OT`W_i]iV9^m\04Q6;HjY0j(+P![BKrKa5k]6>E(
e$-qoUL]o?Yn6NC.;Y/A\k/9OHi`_!Bom1ljIM2_#%0W/U:WhSjCs]mNjKBbap=s0
&t9,t-.Bpb7u`Xu\S/m?etPh&l.7euaH9RQ_Qa(q-R_9(8W6qo@UtTGe4^kfnYt8k
k&X&hNGNYMM&aVI*Tj@pD2u1s2E;CG"p!MRU5ENK!-F7rfBi7,Af>,R!ltk.cUfZV
H;=SZ@R!c>+IrMT>s=PjR*Vd\Zk'PdM(WdGH-@)RmPX$-B>5OF/n*O^$!JRcEui\(
pK:Yt#+0YP0?,>PJ9)aZ:1(<.Wq3b[R0c*11X*C)OlH8AftKN0l$,3eq\*_*>=4qH
oDp]t[d:H\`Pe7:E/(BIKIUjrqPZUh%JorFs15!VE;ED__7W-@&*(ft.p&lY\b^g@
cN*$l'-.~>
endstream
endobj
144 0 obj
[/Indexed/DeviceRGB 255 145 0 R]
endobj
145 0 obj
<</Filter[/ASCII85Decode/FlateDecode]/Length 428>>stream
8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0
b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup`
E1r!/,*0[*9.aFIR2&b-C#s<Xl5FH@[<=!#6V)uDBXnIr.F>oRZ7Dl%MLY\.?d>Mn
6%Q2oYfNRF$$+ON<+]RUJmC0I<jlL.oXisZ;SYU[/7#<&37rclQKqeJe#,UF7Rgb1
VNWFKf>nDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j<etJICj7e7nPMb=O6S7UOH<
PO7r\I.Hu&e0d&E<.')fERr/l+*W,)q^D*ai5<uuLX.7g/>$XKrcYp0n+Xl_nU*O(
l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~>
endstream
endobj
140 0 obj
<</BitsPerComponent 8/ColorSpace 136 0 R/DecodeParms<</BitsPerComponent 4/Colors 3/Columns 500>>/Filter/FlateDecode/Height 500/Intent/RelativeColorimetric/Length 23780/Name/X/SMask 146 0 R/Subtype/Image/Type/XObject/Width 500>>stream
HSTgv5ZŴMLkXD3 ZQ3^(HenS#sO~ * B@/!z !,Z]A!=V}+BY@% !N}aBS@sA_b7ЫFN! :#k*q!TY"&BsA"٠G1<B* zQHu#!@Ov %f+N+!d#A
:ŠWt !t q"N:!NuDЩ'ΠM
t!Md犓?R{S$N]|~&٩vr[7/o[=wt
ȑ#FgWCBj8u}[W@@"WImtL{>s;ti5ʢl[azcw_թn!V]hx0\?lKX?<ßҾHN{tK辚HAd̉c.&wGg)M+yэ!Aw4=·67)S>8"8/p!Ab5Gf[6;zWfϡk'?ϦEaG7j
`LS458W~6-禆)aѭ"G15S!m{Sɴf<[`@7o4LQ[/OԸZrsoї "t6TF3^<ϝcQ*>m<sdؐd7_6?k##cќ6]>BL]b,F ^mnR;FWƭVyt $>d'Z[LɛQ,aG7
dk&5s-&to<ЙZt/ 8BI/?Hֵjǣ;JȺAf%Bհ?T͗BXU@5.ʲ44*-oseA@cYbۗaP&|yt`B ]%_1ZUֵ{Fg@t)=&
6,AIm-ЪuKxR+úNRuÞgo]
頖ЈʩNb&Ȩ#3HJC-&N@mh@w8t0 ̏:&yt߀n<?茿!5S? LIĶZt_:Ľ]DZC5
68u^v@?
X V\.qd[.ry݆Г@,:eQ.{u+^Sjh<0?];y-+u^x)]g/~pF6;
+L
~OLkئwX lVK8'-kp^֠7X lDT[=R.koS}G/jT?# /)w|,z?H
~<BҊx72qbPQ'HirNT71x
)5+!$ *s*xDn/}&e?YTB`@&D$I
51 $idakJxUHKC"?`sIx(zzDxIȏVvYK5D1ljwFJx.&=
x.K$z)HNQ2lDiZV~d%CHݡ]J-ɛQ!5"CfltQv_Rɉ&uћD*db)Zm̶2oܦF|G(}O2;[[ ؗRz%$Wi(_6~xħuiFoYRwy]fֺ$'"kBZ9#PjW=H3z*HK3Sj{/%^dYe<:BҪ0rN/h0O;%]8z[9:9ԁֵ{՜.=#s\A|pO8usKir^5"i3jq~<۔:\W*ms4rNx)eVNPw5L)=KY 9 g.t)Kz_) #PEAFg饔 vB5R_܊K9 w4<5y#*ags$\ixV)˰zl[v)mWC&6D8Zڎ@o0^wdRJѮޘ[B[a~gRZOE fiħH)7Sfz-Er.t)iD
!謩RoS23Wu+QJsث:#荴$^$-x(NMRZSoQŮ6/<uR ֺYhꤠ2z!ǝfǩKKwOGf}X4gӚajJ+T#^>xd䉞Xgw$J/,O/kSF*KTj,\mJ
8aQ ۇeU"Bs{;EOP`[SqEFV|Jw|XcpQQ
K#ڧ:KKNttۘ֔ pŧlwV>vyH,;lK'FGp9YX6 @w";3b=ms'7%-baɜ;[۠\-ΨloB^ܝq{6P~|%<taK46RRfl I=U!F
j1jsna{4+u'T:e6
y2;bsZnoMAV1jCs:Nmm=C |?sG&O
-̅
SzmX$bU0/lfpEGnܺѲgof`3?2Jݶѱ*Uv=֨pyz
s{{U>,4hxL^:2U; RkJ}N4hVL:v鵎,~jswVx/є]{Iw\[jI7Z5zv슮oovx]Ňr cStV`ϩ>Ӕ:$)sZZ;+;K~n;^x ug4~ii|Ƈ^^lbTR'
M 10ڣ-6ѯΫZ%>prt~3ъo#K
`
zRJ۾&ʬKh#lϛ:1 9wm6أCtljmY?е
ŀ?>1S@5v6we}|O:-tTF_0~,OC@%g_\w*ussnHO3<:^G7P]VjHrrҗdN!t6 &R0dO ZWjn'CM@|l+X0:
5MJ/hR%Nvyj
bI%?VH-t T:+E>)fdMMs6j%|ٺu9: ۻ Սe$VjtZ,n'}a*p+O~xCN
MlQ.#I$r%f4BVFOq
ftdb)GlP" 5SqQ&19K$Ʒ)bT"#%Zl2lfqh@`Gm5+VEkV0PVhe}9So
PY!ÖaeVDA,j>̦|UN& D
{ `0& {]y*t+fM.5[!:m]Q
cI^WPۄi0LsD1IZW4Kr{='MyzޑQ}"\/$ cZZ1:ut+fMD= q`t
Szڸul]WrK?ytϐ s #$wWeV̎\K N|lOKI̵vaxi7z@4zOmOE
a&et\~ݍY{=Nazg@T#BQ&
}2:{H[2l^D$NȂFހB^`zvF*%HE:Ȃy(
)霽mh
Sb팬d-,1RX?DF+]Y#_֡R1:?D-%8@^']Y%wW
)|wIG}6fFb 5AV'p1rz]2lIJ7ó~ћmm}YF,qr˚[Xp:X_[J݃Ac˫;^Sg2Ea짆e"7=R$1o/(U{RDw[!jI(f~mP7ܛ+?~f@vC|Sʿ
{SغX3jn7 *Ecd_L}8vhWjU);ߟEJg=[V=ɞ;͉?y4_9a5/+7I牟C'W]RLvǶ戾L&/8Z^懎?GۨOdcf7|ߦ?WD1I>c{ftUJ.D7A5f D &r]柦tk.]]Zj:edՏV$Onm)Xq{!'ޭ4[رX2]=$;7mop~2Fr z]. ?~:@1yA5)|抂"9zdV*lK4<Yg_Ѝb.`M _$$q\+dEΔJWEoν֞W]r::I;NU;mjtL@Ɖ6ȤFdE%RDe)k,v]=oݔfX.y9gs+_i+<"6(= Uc ~){%Ǜ3EVjװcU~!.#}iDצ*8\`~)%,V.ֿnup?VmTPKzD}0xi/Bh_+z}ܝB*T݊NGO}5^N:
[?^S*uLd~#N98IMmķۜxuO} j&W
]|+#JrE'K9V;Y*79'xiP0xe;7Hpbb7zYP8\|qB-K928%^o]nty=-7>s %hL$%M$W;G$"*/H^ gsS.O9=W&.ƪ'jsH"@i|W֖8G-Ķ $"_3Oqy_aiD.t'p7ΐMLb뮗Şм\~.+g
dȬ b 3䙳iDw'άلdrZ<:֥K?Ǿ{}
!B8r/3y'G2YGKKHzǩՈ=Iu@3S4.LStrNy+BaiZw붠ui!]e?z(㭤]/OB^
Ƿ(fD&=&KUf8$/3ݜٻ'nhX0=J"q _SHF(4 SnwZwzbڛ5u= ipƦs%S29UAX_U-'9g"De;iA2H
]Űjx`1lbywbO
u3=m2(<7Zֳ}c+VtC7-抬*%fB+&M bOBd陓ͦW5Цc؇gM"[#vQ=̂
V7.0g d뙞F"|g1BX
{.R>تht t>AJnS;`wd}m4Qb8trȿ= fAn[v-{ﱻB2UTa{$&.7}i<.l5=r{r{791fre-A>Nyc9UNB70#4U7P*w
-~vס·h1%tƧצaKNI؏7g~FՙT=Iu;>F/^C$+,Ny# KN=Ky+̚M\ YUXInJF"D.Wybf4ٱƈS|-9{>o#Q:rQu^cف"$SFcO8j(5=:gM/q{\ڏ_vmm0cQ]PX1>ؓZ{R5PD4QB?l85.HK}9eʀIu$Va;.v
}8x6wͦWurxCLb]=mD7*?
WۉLJ˔W8n*"XSe(tO1\B7*?9ezbt2%<zݥ>.u73KRB:g%q]0n|#Z̚MeJ6Vtb|I Q!
^oEw/SZw ^;>i
^
db,
;T9a)(B>v_I˚1G'xuU05/R\k&0\Ftr¨)ح.|oxYs7E{t{=_bR!^cR/Jg`Ui?+D/kj7Ftu?;i!o>܃1W9A UG5˚|2\lݍUb7@wΒGzT5Z2>iЃ.UNx{#F/kr|17luD{L9!E`K7Ql%\D){,Vb'2ԗ.^1\ooJrx\ףi!|G*'"dݺ
pWD :obƲ# `wc
`uυYѽY+n붠W#採z}4;C!Ϻdt1 <&qx4t`<]"}UC
o~j21#l!ȥmnLt|4/FW+:3˧iUj,NhL- _Qħ]!{NVt*Gm8^Ot9oP7l-LH;cǯZՊN/`bo6ufCZ-ȠbL
Fa'#ՊΧ k:t*Cvݖ`XD
At-CCѭ7hr4H/튎"JSSg?Ngӝθ^:UXٖeRU.EZ("@#4Hȅ IN٠;t<yNW>y;-]]1UN`
7Ap|èf%:,pRU~w\a9BSV3ъt[eO_EmT,/EWKH[rָ T={c}MS ^Rbx7Hr+UL
Fӽu+私{Ctr5 NN-ޡSFBMf~WB 4XnDJ`ѽogd ^<Uiߞ&$j~yFl1teE>ۻNt֛ioF/>}aNrGrִ ܓn36IuJ*NϜ=|5StOK:>8ܓk{]+2zb/tG3l<ݑ}QnrKIKފ왧WFW@d>a;{ȧ۟M =3G@VXZΤPEr:bݲc>Z^rAXCo 8&vȪG/ {+;6ZLȧ{e:
ځcRldV"ͨ$9U1>\Mc#+m#q(iǙi~SRl7-tӱ1MD:
(p[$j \n,1(Zb]toE8U\u6Ԁ|wmrZB]=Kp6xtl7F]b3˩w/=X.(4XVeؔm:THVd[/әtn\
f{Ȩɞ5N\i{ P"m.F۶Йe5A-h
FQ<э%&˰/JDVp!zcaXbIr+1X01ii+ԒlOJ[ޤc+O[jzw!XnȒٗQE;C-TӍe56C(~g[]v^,TjLו[s?jQG]RӍeU( tl7nGgZӪ<1Ȟs>=ܧqN@ͨyȲ2jtWՐ{Q=RWH:v`F|q~oGL9騪r;q)YPce
:7YV) (߶gd-:ż0g(xzʮYQjJzSn,+o 1߶
ti݁u.za@V JYC-U. $XVs3bm8]Zy)\ZGUUͮ@m0=Xf־gRm3bmOJOIVp'D{閪cmM
Ce@nfqљt y~lҊf5`tKVus4}`tvp9{yy
:>dCm2aw6۷-UaȾUzZYN 7LRvj
'ؾҊ[Ug?8vgr_Xo{,ܯegRJJ?y}NgV|3C+ڜ]Tj1K+tf)"iԆy_ҥ߹Qz Umm_pZRZj>ךtf)MQvYh]ZՌ6ڮ~̬>r*iSWn0HgY0?V:!FKt?U9ܜs
C5Z΄},*"fp:YJdh/OW:,_ډ%:qÎY0?ҙަפit<RWP{ʹ_˳+]Z[Q*cK+:LSesZo{*`Ij@߶?p7ҊR~TsȹP]TjD
Wjљ˲LgRȀz3vZRZj)@i\3Ki5R%9EgR2#nzs"|d[btf)W2Ѷǒ!:z >ݧ^*9nUjQ̳Oҙ
*hۗn:+dnuBb2
ۑ=؞Ltf)Y Ȁ
Yy))Bw\/s
yEDTم={>JV|QԀX}tiwXjJv%g|nOf*iZkbCjK+[!$Rs3T$j^.Ҋv3v&]ZA<t3K(d&6lGcu.<ж6_K+C<q"'Q3Kӥ1m4K+"zc:"㓨,Ktiw,f>eҊqHΦwYc*t5Sr)s.>-UE랫ISٓL+|Q c-FV|{/@Sжy.z:!u˩݅~j
|Q YO.>`m/x.}NTy2O4_EmTӇ}q9EV|lW^ZCV|0dzt<\eՕRc||FcIJ"q\TL=1* (ʦC
EEYzivriF6o=_9RO+*.ηѴॅ@boa۾]xicثu|吏qFGH<rdE=2lv^}3r*/s>ˀM4m
<Oq$*>'TҾ0*<3i3AHZs 'W_~B@Uu叜inkzxE KTvø4 xi#=vF ?">LJqi; )vIIeJI.e<e@8NOf43̐;o]2ҧt'sogAi쩤m{xi#D\$J9
W@vsˀoc#]<љ]mK˟^T.o53>tjfھ8Z?Ib&xNDl̨ATdq%</4i%dxf!(dilZWK˟)8ZƙʊTj s,2}9BӦp,v!l7?:.xQuB1K48v%|IJO2}I~_Hg69ص^Z<$£ZG9qHy;SށdRA&|Y$9g`^8<z ߴҗpjh<\8H,G2Mig?9ION=O2U=6{BoqFGXv!Ux$ӔA'$='{K˟$
^@tkK8YJQمd:9Bxi#LbOn{QWf{nxZtzNk>{s[o\ݵp|/-v[iQQxiSr=GdV,MCA]D'e%/:ÙkadS9/-TiQq^ZNbϗp9mz+_>m4pq#5wL;nFϦ
/-iQ/-uDb/.uRd^5xx×l9¸qk+& /-J~nK˟-XlYo,\GWx,&F|-ӋȢ[f?n4g8ZNxc!15&5ST_;50na_4\(|q60K˟~*mzॅ
ZI͞g=o6cPk.0nVL#zi.JJ~|i3bTCBq3˟bڸ_/_srcFdE-n73]hDv\YvODJvIN3RG"?>ܱןg̊JݣfCfF1,_P1HvEUP7G"ЗpK˙:QxWi9ǂ?2.PLMۀY͇J֯?g&'+R=xoy2*$ Uo<=^.u㳙#첟LU,<dӷ}gϷTc-7mdEGhPG[d\Kf$<YpJS ~'
IبSO.76m$E<gxꜫwھ=/-v䙩)h{B'IF7@+Tz?>:o]ltf!d1\ agBD/Wm͖ۖj*<:6ˮL#BhqLM=BͣrXlYooU^]Q[>.+$1.EQԒv|?L4ٴ
:1xf!Jk/q>nOUuw2suU*:4R<%
3Bxl S9LEbYZVۛRj;5 IBi>}/׀>{lBpyTSvEQE18tE
gf9=B'HJ}5uÑ xfoL~Sh<$U>k ^quf$E,\[x}56
9ՠ(<:c1tRn
,GǨ6G85dcZ5?SH-U.MwBjry>
,]9TG7N
,Z[4?ܶd5EdY[ʡw[3aƥ(Ð8SLKBܦى+j`|ob}5߈ϩp
^ͦFxfѡqQ"}KCu;,
J㥩MEH!LꇢfE&ʒۛg¿k4NNEQ?~J^cBM+Ӛm$!y;\M.]Br튢|vxf!Kٓ,6zP`z/!GHsU0)g?!Lb*۞mk{cEDQi`d
L[YJ ycjX2W'6+"Q`?{l4|h<(:4 Y&SzVq$DV8C
䘧Bhr0/0}&xcQT<8Hћói8)|2vGic5\5,E2O'mwmƢJ*ɩoǙ,'ߴ}aB|]iQ$_OMgjvJwΎ;VM/kT.
R.DXV\ȍ\='99ۣFs'[{&<|ScF7-{J`
+v^L;[H䨿 ]r(7n~9
aGT }_L&\R`M˞ T(v)Ew,zVyO(^ ;RyǡV/E,"vTtx6Y>%^>/g;Olw@Wd &n>Vb0wauc ߱(\ ;ڥ=-Sa
tbWI7n1T'swF|`9[zO-@UH㼸ݴZ8zxFH|-T:^tQ)bD2lVbAo#ZՋoZƔ# U!
k=GM`
c|]jRxߔǾ2~kPn4R&Lo`@'?vtӲga*kRݱ#V&bܸ]sQB$,|܄%ǡ[-K!(whM˘=guVr]ECW
Xf<sg$s߅SE'l.<!euÕ0s]|`+`~V߄.\v^ܷQRdHߺl0{j{3
Vt2b'Cw?F,.z$TRѽO?4J,.[7! ]hq vfn'k݀.XDjO@)ś0]oDWT?LQJFmw@~p:ݫ4'`gvݴVY& K\kv)'v`"t{͈^ּ
b,w߽rh'~vހ.[ƜovnE,.}&>]q]>Űm % GK;
P-"e"խNң}JiYZ`f;N'
<le̥_N.nԻHvEgOt/ӽܸKdn't3G<ab-lY!KLKb=ݏ/@+.Uc_)E7W36سnc{4˖1`;LtWˁcnJÂ}ts^$$_2NO0JU6ᇬ(!sesјYAq;jC+:3? V*exy/MT͌'/o~çoƀt'~t2gaFrw; %A+.w!.D*JOQf( QVç' qB2˖%+M"cfP._J[=E<F7,FvdkifFfCe"q1aFHnrVt7$ *-bW6E,%x[l .+'KfIu/bWUft߲^
];nOqtC6Vx&