1297 lines
672 KiB
HTML
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Mario Paint Music Composer - danielx.net</title>
<meta name="description" content="This Mario Paint inspired composer tool is easy and fun. You can create simple and beautiful songs right in your browser and share them with the world!">
<link rel="shortcut icon" href="assets/images/raccoon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<script>
;(function(PACKAGE) {
var src = "(function() {\n var annotateSourceURL, cacheFor, circularGuard, defaultEntryPoint, fileSeparator, generateRequireFn, global, isPackage, loadModule, loadPackage, loadPath, normalizePath, publicAPI, rootModule, startsWith,\n __slice = [].slice;\n\n fileSeparator = '/';\n\n global = self;\n\n defaultEntryPoint = \"main\";\n\n circularGuard = {};\n\n rootModule = {\n path: \"\"\n };\n\n loadPath = function(parentModule, pkg, path) {\n var cache, localPath, module, normalizedPath;\n if (startsWith(path, '/')) {\n localPath = [];\n } else {\n localPath = parentModule.path.split(fileSeparator);\n }\n normalizedPath = normalizePath(path, localPath);\n cache = cacheFor(pkg);\n if (module = cache[normalizedPath]) {\n if (module === circularGuard) {\n throw \"Circular dependency detected when requiring \" + normalizedPath;\n }\n } else {\n cache[normalizedPath] = circularGuard;\n try {\n cache[normalizedPath] = module = loadModule(pkg, normalizedPath);\n } finally {\n if (cache[normalizedPath] === circularGuard) {\n delete cache[normalizedPath];\n }\n }\n }\n return module.exports;\n };\n\n normalizePath = function(path, base) {\n var piece, result;\n if (base == null) {\n base = [];\n }\n base = base.concat(path.split(fileSeparator));\n result = [];\n while (base.length) {\n switch (piece = base.shift()) {\n case \"..\":\n result.pop();\n break;\n case \"\":\n case \".\":\n break;\n default:\n result.push(piece);\n }\n }\n return result.join(fileSeparator);\n };\n\n loadPackage = function(pkg) {\n var path;\n path = pkg.entryPoint || defaultEntryPoint;\n return loadPath(rootModule, pkg, path);\n };\n\n loadModule = function(pkg, path) {\n var args, content, context, dirname, file, module, program, values;\n if (!(file = pkg.distribution[path])) {\n throw \"Could not find file at \" + path + \" in \" + pkg.name;\n }\n if ((content = file.content) == null) {\n throw \"Malformed package. No content for file at \" + path + \" in \" + pkg.name;\n }\n program = annotateSourceURL(content, pkg, path);\n dirname = path.split(fileSeparator).slice(0, -1).join(fileSeparator);\n module = {\n path: dirname,\n exports: {}\n };\n context = {\n require: generateRequireFn(pkg, module),\n global: global,\n module: module,\n exports: module.exports,\n PACKAGE: pkg,\n __filename: path,\n __dirname: dirname\n };\n args = Object.keys(context);\n values = args.map(function(name) {\n return context[name];\n });\n Function.apply(null, __slice.call(args).concat([program])).apply(module, values);\n return module;\n };\n\n isPackage = function(path) {\n if (!(startsWith(path, fileSeparator) || startsWith(path, \".\" + fileSeparator) || startsWith(path, \"..\" + fileSeparator))) {\n return path.split(fileSeparator)[0];\n } else {\n return false;\n }\n };\n\n generateRequireFn = function(pkg, module) {\n var fn;\n if (module == null) {\n module = rootModule;\n }\n if (pkg.name == null) {\n pkg.name = \"ROOT\";\n }\n if (pkg.scopedName == null) {\n pkg.scopedName = \"ROOT\";\n }\n fn = function(path) {\n var otherPackage;\n if (typeof path === \"object\") {\n return loadPackage(path);\n } else if (isPackage(path)) {\n if (!(otherPackage = pkg.dependencies[path])) {\n throw \"Package: \" + path + \" not found.\";\n }\n if (otherPackage.name == null) {\n otherPackage.name = path;\n }\n if (otherPackage.scopedName == null) {\n otherPackage.scopedName = \"\" + pkg.scopedName + \":\" + path;\n }\n return loadPackage(otherPackage);\n } else {\n return loadPath(module, pkg, path);\n }\n };\n fn.packageWrapper = publicAPI.packageWrapper;\n fn.executePackageWrapper = publicAPI.executePackageWrapper;\n return fn;\n };\n\n publicAPI = {\n generateFor: generateRequireFn,\n packageWrapper: function(pkg, code) {\n return \";(function(PACKAGE) {\\n var src = \" + (JSON.stringify(PACKAGE.distribution.main.content)) + \";\\n var Require = new Function(\\\"PACKAGE\\\", \\\"return \\\" + src)({distribution: {main: {content: src}}});\\n var require = Require.generateFor(PACKAGE);\\n \" + code + \";\\n})(\" + (JSON.stringify(pkg, null, 2)) + \");\";\n },\n executePackageWrapper: function(pkg) {\n return publicAPI.packageWrapper(pkg, \"require('./\" + pkg.entryPoint + \"')\");\n },\n loadPackage: loadPackage\n };\n\n if (typeof exports !== \"undefined\" && exports !== null) {\n module.exports = publicAPI;\n } else {\n global.Require = publicAPI;\n }\n\n startsWith = function(string, prefix) {\n return string.lastIndexOf(prefix, 0) === 0;\n };\n\n cacheFor = function(pkg) {\n if (pkg.cache) {\n return pkg.cache;\n }\n Object.defineProperty(pkg, \"cache\", {\n value: {}\n });\n return pkg.cache;\n };\n\n annotateSourceURL = function(program, pkg, path) {\n return \"\" + program + \"\\n//# sourceURL=\" + pkg.scopedName + \"/\" + path;\n };\n\n return publicAPI;\n\n}).call(this);\n";
var Require = new Function("PACKAGE", "return " + src)({distribution: {main: {content: src}}});
var require = Require.generateFor(PACKAGE);
require("!system").launch({}, function(config) {
require('./main');
});;
})({
"source": {
"NOTES.md": {
"content": "Direct Download\n===============\n\nWhen people purchase via Stripe we receive a webhook on AWS. We send an email\nvia SES with links to a direct download.\n\nTo update the direct download we need to update the stripe-webhook lambda after\nuploading the new files to the composer.\n\nYou can drop the builds onto Composer Uploader in apps.v2, then copy the links\ninto the lambda here: https://console.aws.amazon.com/lambda/home?region=us-east-1#/functions/stripe-webhook?tab=configuration\n\nSteam\n=====\n\nPublishing builds\n-----------------\n\nUnder SteamWorks -> SteamPipe -> Builds https://partner.steamgames.com/apps/builds/1208550\n\nThere is a little link to an upload button. \nhttps://partner.steamgames.com/apps/depotuploads/1208550\n\nUpload the zip containing your app's files.\n\nNOTE: if there are pending changes this may fail until you revert and try again.\nTheir system is very frail.\n\nOnce uploaded you'll be able add a comment to the build.\n\nSelect the build from the builds page and click \"Preview Change\" on the build\nsetting it to the `default` branch (or some other test branch).\n\nYou can verify the builds by clicking on 'Depots Included' and looking at the\nsha1sums of the files, comparing them with what you have in your build dir. You\ncan also compare them with the app in your steam content:\n`C:\\Program Files (x86)\\Steam\\steamapps\\common\\DanielX.net Paint Composer\\`\n\nGo to \"Publishing\" click \"Prepare to Publish\", then click \"Really Publish\" It \nmay take several minutes for the update to become available in the steam client.\n\nWhen publishing for windows and MacOS if you have made separate depots for the\ncontent then both need to be included in the 'Packages'.\n\nIt should be possible to update the depots for win/osx independently\nusing the uploader, but I haven't tested it.\n\nPackaging\n\n- Header capsule image 460px x 215px\n - Design: This image should focus on the branding of your product. For best results, please use the same artwork that you use for any retail box or marketing and make sure the logo is clearly legible.\n - Usage: It will appear at the top of your page in the store, in the 'recommended for you' section, in 'grid view' in customers libraries in the Steam client, in browse views on Big Picture mode, and for daily deals if applicable.\n- Small capsule image 231px x 87px\n - Design: These are small, so they should focus on making the logo clearly legible, even at the smallest size.\n - Usage: These are used for all the lists throughout Steam. Search results, top-sellers, new releases, etc.\n - Sizes: Please provide '231px x 87px' image. From that, two smaller sizes (120x45 and 184x69) capsules are automatically generated.\n - Requirements: Small Capsule should contain readable logo, even at smallest size. In most cases, this means your logo should nearly fill the small capsule.\n- Main capsule image 616px x 353px\n - Design: These should be designed to market the product. For best results, \n please use the key art and logo that is being used for any retail boxes or marketing. \n Avoid using quotes or other strings of text beyond the title of your game\n - Usage: These appear at the top of the front page.\n- Page Background 1438px x 810px\n - This should be ambient so as not to compete with the content on the page, so \n try to avoid high-contrast images or images with lots of text. A template will \n automatically be applied to your uploaded file, which will tint it blue and fade \n out the edges. If you don't upload an image here, we'll automatically take the \n last screenshot and generate a background image from that.\n- Library Capsule 600px x 900px\n - This should be graphically-centric and give the user some sense of the experience.\n Please use the key art and logo that is being used for any retail boxes or marketing.\n Avoid using quotes or other strings of text beyond the title of your application. \n The art should immediately tell the customer something important about your product. \n The logo should be easily legible against the background.\n - This image is primarily used in the library overview and collection views\n- Library Hero 1920px x 620px\n - The hero graphic and logo are layered and move independently when the page is scrolled, creating a subtle parallax effect. Consider how the product logo will be placed on top of the hero image (left bottom corner or centered). You'll want to ensure the logo is both visible and legible against the background.\n - This should be a visually rich image that is easily recognizable. For best results, please use the key art that is used for any retail boxes or marketing. Avoid using quotes or other strings of text.\n- Library Logo 1280px x 720px\n - After upload, you can use the preview tool to select the logo's position. Options include: left bottom corner, centered top, centered middle, and centered bottom.\n - Note: If a hero graphic and logo are not uploaded, the hero area will display a screenshot from the store, with the application name in text overlaid in the bottom left corner.\n - For best results, use the logo that is being used for any retail boxes or marketing. Youll want to ensure the logo is both visible and legible against the hero graphic background, sometimes a drop shadow can help. The PNG image should have a transparent background.\n - Appears at the top of a user's library details page for this product, placed on top of the hero graphic.\n - 640px x 360px PNG, or 1280px x 720px (for high DPI displays; not required, but will be in the future) Ensure that the logo extends the full width of the PNG, without extra padding.\n- Community Icon 32x32\n- Community Capsule 184px x 69px\n- Community group header 444x208 \n\nAt the center of the template is a \"safe area\" of 860px x 380px. This area will remain uncropped across scaling and resizing of the Steam client window. Artwork should extend across the entire template, but critical content should be within the safe area. For ex: a main character's face should be entirely in the safe area or risk being cropped.\nUsage\nAppears at the top of a user's library details page for this product.\nSizes\n1920px x 620px PNG, or 3840px x 1240px. The higher resolution is accepted and used for high DPI monitors. They are not required, but will be in the future.\n\n\nUsage\t\nThis is the image that will be used in the background of your store page.\n\n\nElectron\n========\n\nCreating an Electron app from a standalone ZineOS app isn't too hard.\n\nAssets\n------\n\nRefactor app to make sure assets come from a consistent place.\n\nDownload resources to `assets` folder\n\nUse `webRequest.onBeforeRequest` to redirect assets to the local path.\n\n```javascript\nsession.defaultSession.webRequest.onBeforeRequest({urls: [\n \"https://danielx.net/composer/*\",\n \"https://danielx.net/fonts/*\"\n]}, (info, callback) => {\n callback({\n redirectURL: info.url.replace(/^https\\:\\/\\/danielx\\.net\\/(composer|fonts)\\//, ASSET_DIR)\n })\n});\n```\n\nA future optimization will rewrite the path conditionally in the app\nbased on the environment, this avoids the extra redirect request and shaves off\na few `ms`.\n\nElectron Env\n------------\n\nSetting `nodeIntegration: true` enables IPC so the web process can talk to the\nmain process. We also set up a global `ELECTRON_VERSION` value in `preload.js`\nto provide an environment var to enable electron specific behavior.\n\n```javascript\n// preload.js\nwindow.ELECTRON = process.versions.electron;\n```\n\n```javascript\n// Create the browser window.\nmainWindow = new BrowserWindow({\n width: 1280,\n height: 1024,\n webPreferences: {\n preload: path.join(__dirname, 'preload.js'),\n nodeIntegration: true\n },\n autoHideMenuBar: true,\n fullscreen: true\n})\n```\n\nIn the future `system` library could enable host FS and other helpful things so\nall ZineOS apps would work great in an Electron straight out of the box.\n\nbeforeunload\n------------\n\nElectron `beforeunload` behaves differently. It won't prompt with a string but\nwill cancel the closing if `e.returnValue` is truthy.\n\nHere's a pattern that works well:\n\n```coffee\n# Override beforeUnload for the electron world\neditor.removeUnloadHandler()\nclosing = false\nwindow.addEventListener \"beforeunload\", (e) ->\n if closing\n return\n\n editor.confirmUnsaved()\n .then ->\n closing = true\n remote.getCurrentWindow().close()\n\n return e.returnValue = true\n```\n\nThis can't easily be reconciled with the browser style where we cannot use an\nHTML prompt, only the browser's prompt. In Electron we can prevent the app from\nclosing entirely (the common browser pattern does this!). It may make sense to\nmove this to the system layer if it is common.\n\nUploads?\n========\n\n const Crypto = require(\"crypto\");\n \n const HASH_ALGORITHM = \"sha256\";\n const DIGEST_ENCODING = \"base64\";\n \n exports.handler = async (event) => {\n // TODO implement\n const response = {\n statusCode: 200,\n body: JSON.stringify('Hello from Lambda!'),\n };\n return response;\n \n const AWS = require('aws-sdk');\n const S3 = new AWS.S3({\n apiVersion: '2006-03-01'\n });\n \n // Compute request body sha\n const sha = digest()\n \n const key = \"_apps/composer/\" + sha;\n \n // Post to S3\n S3.putObject({\n Bucket: \"whimsy-fs\",\n Key: key\n }).promise()\n .then();\n \n };\n \n function digest(data) {\n const shasum = Crypto.createHash(HASH_ALGORITHM);\n shasum.update(data);\n const sha = base64URLEncode(shasum.digest(DIGEST_ENCODING));\n console.log(\"DIGEST:\", sha);\n return sha;\n }\n \n function base64URLEncode(base64String) {\n return base64String.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/\\=/g, \"\");\n }\n\n\nScriptoo\n========\n\nScript to adjust max-age for resources.\n\n system.readTree(\"/My Briefcase/public/danielx.net/composer/downloads\")\n .then (entries) ->\n entries.filter ({relativePath, type}) -> type is \"image/svg+xml\" or type is \"audio/wav\" or type is \"image/png\"\n .then (entries) ->\n Promise.all entries.map ({path}) ->\n system.readFile(path)\n .then (blob) ->\n system.writeFile(path, blob, cacheControl: 86400)\n\n\nSound Pack Format\n=================\n\nI want a simple format that will contain 16 images and 16 sounds in one blob.\n\nThere can be an editor for the sample pack separate from the player. Start\nvery simple with v1, maybe add audio loop points or crazy stuff in v2. The goal \nis to point to a URL for the sound pack and with one request get all the \nresources the player needs.\n\nPeople could swap out one sound pack for another. In the future songs may load\nmultiple sound packs.\n\nImages must be 32x32\n\nRough Format thoughts\n\n- [0-3] Magic identifier\n- 16 times\n - 4 byte offset, 4 byte length\n- [132+] Data\n\nWhat about abusing a .wav file to store all the samples and a single 128x128\n.png file?\n\nData Types\n==========\n\nA note is a number. `C4` is `0`, `C#4` is `1`, `B3` is `-1`, etc.\n\nA phrase is a collection of (note, beat, instrument) tuples.\n\nScale is a collection of notes modulo 12.\n\n2019-05-18\n----------\n\nGitHub removed anonymous gist support last year: \nhttps://github.blog/2018-02-18-deprecation-notice-removing-anonymous-gist-creation/\n\nLooks like better integration with whimsy.space is the way forward.\n"
},
"README.md": {
"content": "Paint Composer\n==============\n\nCreate music online with this simple Mario Paint inspired music composer.\n\nHotkeys\n-------\n\n| Action | Key |\n|---------|--------|\n| Select Instrument |`0-9` |\n| Select Instrument | `<backtick> 1-7` |\n| Select Pattern | `Shift+0-9` |\n| Eraser Tool | `e` |\n| Selection Tool | `s` |\n| Undo | `Ctrl+z`, `⌘+z` |\n| Redo | `Ctrl+y`, `⌘+y` |\n| Play/Pause | `Space` |\n| Play from Beginning | `Enter` |\n| &nbsp; | |\n| **Selection** |\n| Copy | `c` |\n| Cut | `x` |\n| Delete | `Delete` |\n| Reset | `Esc` |\n| &nbsp; | |\n| **Data** |\n| Save | `Ctrl+s`, `⌘+s` |\n| Open | `Ctrl+o`, `⌘+o` |\n| Export | `Ctrl+r`, `⌘+r` |\n| &nbsp; | |\n| **Meta** |\n| About | `F1` |\n| Help | `?` |\n"
},
"TODO.md": {
"content": "TODO\n====\n\nPWA And Direct Download\n-----------------------\n\n- [ ] Mobile Styles\n - [ ] Move about and close button out of tools\n - [ ] Toggle tools grid full-screen open/close\n- [ ] Track Purchased State\n- [ ] Activation Mechanism\n- [ ] Download / Cache resource files\n - [ ] Track resources in resources.json \"db\"\n - [ ] Add demo songs to offline cache\n - [ ] PWA\n - [ ] Windows Build\n- [x] PWA immediately cache index.html and lame encoder for offline support\n\nFix F-pitch\n-----------\n\nDue to an oversight the 0 note was pitched to F4 rather than C4. To resolve we\nshould add a new version on the song files. We'll need to add an adjustment to\nall the old sound packs... ideally we could find properly pitched samples as\nwell.\n\nDownloadable Version\n--------------------\n\n- [x] Wicked Box Art\n- [x] Stripe Checkout\n- [x] Stripe Webhook\n- [x] API Gateway + Lambda funcs\n- [x] Sending Email with SES\n- [x] Download Links\n- [x] Get customer email from Stripe API -> Using receipt_email from webhook object\n- [x] Success Modal\n- [x] Update to prod API keys\n - [x] Both lambdas\n - [x] Publish to prod API Gateway\n - [x] Composer App\n\nFeature Requests\n----------------\n\nFeature requests, ranked by popularity (+++). Filter these into upcoming milestones.\n\nv0.2.0\n------\n\n- [x] Remove jQuery\n- [x] Remove excess deps\n- [x] Update touch-canvas\n- [x] < 100k gzipped package size excluding audio resources. Currently 53.3k\n- [x] Style Update\n - [x] Buttons and Forms\n - [x] Chicago\n- [x] Publish\n- [x] ++ Right Click to erase\n\nv0.3.0\n------\n\n- [ ] +++ Tracking Saves\n - [x] Download JSON format\n - [x] Open JSON format (drag n drop, file open)\n- [x] ++ Donate / Patreon Link\n- [x] Discord Link\n- [x] Favicon\n- [x] Caching and Optimization\n - [ ] Spritesheet images\n - [x] Proper maxAge on audio, image, and font resources (1d+)\n- [x] Keyboard Shortcuts\n - [x] Instruments\n - [x] Patterns\n - [x] Eraser/Select\n - [x] Selection Conditional\n - [x] Copy\n - [x] Cut\n - [x] Delete\n - [x] Cancel\n - [x] Play/Pause from inputs\n- [x] ++++ Clear All Button\n- [x] ++++ Easier to find a note / Display Note names / Treble and Bass Clef\n- [x] +++++ Arranger View is unintuitive\n- [x] +++ Larger Octave Scale\n- [x] Handle 100 notes per second without stuttering (Sample quality, lack of dynamics is the current limiting factor)\n- [x] +++++++ Export Download\n - [x] Trim excess blank space at end of song (like if someone left the default length, but their section is short)\n - [x] ++ MP3\n - [x] .wav\n - [x] Fixed on Safari\n - [x] Firefox not downloading\n- [x] Cross Browser Testing\n - [x] Chrome 77%\n - [x] Safari 13.5%\n - [x] can't find variable `OfflineAudioContext`\n - Use webkit prefix\n - [x] startRendering doesn't return a promise\n - attach a callback https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext/startRendering#Syntax\n - [x] Firefox 3.5%\n - [x] Inputs in Buttons all large due to flex: property on input\n - [x] Not Downloading Export\n - Anchor tag with a `download` property and simulated click needs to be attached to DOM\n - [ ] Edge 3.4%\n - [x] `Array#flat` polyfill\n - [x] Opera 1%\n - [x] IE11 < 1%\n - [x] I'm not supporting IE11\n - [ ] Object.assign (Need to polyfill at the !system library or earlier)\n- [x] ++++ Undo/Redo\n- [x] ++++++ Selection and Copy/Paste/Delete\n - [x] Pattern Palette\n - [x] Palette Styling\n - [x] Behavior\n - [ ] Move / Transpose\n - [x] Delete\n - [x] Undo / Redo interactions\n - [x] Selection styling\n - [x] Scroll when selecting near edges\n- [x] Padding for first beat, currently crammed up on the left edge\n- [x] All icons must be 32x32 *(they can be slightly wider, long cat gets away with it)\n- [x] ++++++++++++++++ More Instruments\n - [x] Remap old instrument numbers\n - [x] Eraser Sound\n - [x] Original Mario Paint\n - [x] ++ Yoshi Sound\n- [x] More accurate audio timing using `context.currentTime` deltas rather than assuming\n a fixed and accurate `requestAnimationFrame` callback rate.\n\nv0.4.1\n------\n\n- [x] Publish and share via URL\n- [x] BUG: Notes were pitched to F\n\nv0.4.2\n------\n\n- [x] Bugs / Chores\n - [x] Keep playing when window is not in focus\n - [x] Audio cuts out until stopping and restarting...\n - Looks like it is related to a bug in the audio buffering.\n - [x] Drop file to load\n - [x] Sample Rate at 44100\n - [x] Beat count indicator should be one-based\n - [x] Can place notes at beats beyond length\n - [x] Can place playhead beyond length of section on short sections\n - [x] Clear shouldn't change song/section length\n - [x] Fix triplets, clicking after changing note duration\n - [x] Test and refactor\n - [x] Test loading legacy songs\n - [x] Move legacy files to lib/legacy/\n - [x] Test audio encoding\n - [x] Requesting MIDI access was killing FF and Safari\n- [x] Usability\n - [x] +++ Demo songs\n - [x] List Shortcuts\n- [x] Visible UI for sharps and flats\n- [x] Adjustable Quantization\n - quarter\n - 8th\n - 16th\n - Triplets\n - Custom\n- [x] Time Signatures\n- [x] Engine v1.1\n - [x] Pan Default Instruments\n- [x] Global Filters / Effects\n - [x] Effect Chooser\n - [x] Backgrounds\n - [x] Cygnus\n - [x] Rainbow\n - [x] Water\n - [x] Purple\n- [x] Stereo WAV and MP3 Encoding\n - [x] MP3 Worker\n - [x] Wav Worker\n- [x] Mobile\n - [x] +++ Sound not working\n\nv0.4.3\n------\n\n- [x] Bugs\n - [x] Eight note and sixteenth note unicode doesn't display on all browsers/platforms\n - [x] Progress bar display for publish to interweb\n- [x] PWA + Offline Support\n\nv0.4.4\n------\n- [ ] Bugs\n - [x] Selection movement clumps into notes as they enter the range\n - [ ] Flat and Sharp Spacing for new instrument image sizes\n - [ ] Looks ok on Windows but still weird on Mac\n - [x] Stopping playback doesn't fade out immediately (pre-buffered notes still play)\n - [x] Staff lines on top of note background image\n - [x] Cursor offset sometimes zero on first load\n - [x] Clear should maintain tempo and effect\n- [x] Animate notes when they play\n- [x] Multi-section Arrangements ($)\n - [x] Per section tempo\n - [x] Handle different section tempos\n - [x] BUG: Still not quite right on wrapping with big tempo diffs\n - [x] Per section length\n - [x] Update active section based on current playhead location\n - [x] Per section FX\n - [x] Add New Section Button\n - [x] Arranger Panel\n - [x] Duplicate\n - [x] Remove\n - [x] Play sounds from sections\n - [x] Render notes in all sections\n - [x] Section break bar style\n - [x] Song end bar style\n- [x] thicker lines\n- [x] Animate all sprites\n\nv0.4.4.1\n--------\n\n- [x] Display Fullscreen Hotkey (F11) in hotkeys list\n- [x] Display measure numbers in sections\n- [x] Navigation\n - [x] Arrow Keys\n - [x] Page Up/Down to scroll\n - [x] Home/End\n\nv0.4.5\n------\n\n- [x] Bugs\n - [x] Button focus color\n - [x] Active class on play button\n - [x] Measure Numbers illegible on FX backgrounds\n - [x] Sections playing active instrument on section change: https://danielx.net/composer/#api-LvxLyzyjgJE5fIkDvJ1wb2HMBWQIn30Q6ZxJt-nF2Uo\n- [x] Features\n - [x] Load from URL\n - [x] Link to purchase on Steam!\n - [x] Toggle Repeat Button\n - [x] Repeat bar style\n - [x] Periodic Upsell Prompt\n - [x] Key Signatures\n\nv0.4.6\n------\n\n- [ ] Bugs\n - [x] Note / Beat is illegible with some effects, maybe also include seconds\n - [ ] Exit button requires two clicks in Electron\n - [ ] Pattern Preview causes viewport to scroll\n - [ ] Make sure to save/load repeat setting when loading song\n - [x] Placing note in non-C key signature plays incorrect preview sound\n - [x] Resection notes when moving across section boundaries\n - [x] Moving selections left/right isn't working\n- [ ] Features\n - [ ] Native Open, Save, Save As (in Electron)\n - [ ] Toggle Fullscreen menu item?\n - [ ] Multi-section\n - [ ] Payment Prompt\n - [x] Re-order sections\n - [ ] Per section time signature\n - [x] Adjust measure count indicator by time signature numerator\n - [x] Draw barlines per section\n - [ ] Jump to section quick tabs\n - [ ] Undo / Redo section (order, add, remove) changes (not length changes)\n - [ ] Pattern Palette\n - [ ] Styling\n - [ ] Persistence\n - [ ] Remove pattern\n - [ ] Edit pattern?\n- [x] Update build for Mac\n - [x] Fixed Steam Build on Mac!\n\n\nv0.5.0\n------\n\n- [ ] Bugs\n - [ ] ~Flicker during scroll on long pieces. Webkit only (Chrome/IE/Opera) FF is ok.~\n- [ ] Display FX BGs per section\n- [ ] Documentation\n - [ ] ++++++ Tutorials/Help\n - [ ] +++ Help is hard to find\n - [ ] Auto-Generate Hotkeys table\n- [ ] Engine v1.1\n - [ ] Note Durations\n - [ ] Don't allow two notes of the same instrument at the same position.\n - [ ] Dynamics (velocity?)\n- [ ] Custom Spritesheets\n\nv1.0\n----\n\n- [ ] ++ Login Account / Whimsy.space _My Briefcase_ integration\n- [ ] Scale and Zoom (Maybe)\n- [ ] Eighth Note Time Signatures\n - [ ] 5/8\n - [ ] 6/8\n - [ ] 7/8\n - [ ] 8/8\n - [ ] 9/8\n - [ ] 11/8\n - [ ] All / Custom?\n\nv1.1\n-------\n\n- [ ] Mobile\n - [ ] Scroll Tool\n - [ ] Optimized Interface / Responsive\n - [ ] Adjust note after placement left/right in time, up/down on scale (-1, -0.5, +0.5, +1)\n- [ ] Document and make composer an exemplary whimsy.space app\n- [ ] Refactor, use system libs / database\n- [ ] Graphical FX Editor ($)\n- [ ] Engine v2 SF2 + MIDI\n- [ ] Renderer v2 WebGL?\n\nPaid Version\n------------\n\n- [ ] Packaging Content (images, descriptions, etc.)\n- [ ] Electron App\n - [x] Steam\n - [x] Direct Download\n - [ ] Windows Store\n - [ ] Mac Store\n - [ ] Touchbar Support\n- [ ] Custom Sound/Image Packs\n - [ ] NES/SNES Samples\n - [ ] Hi hats\n - [ ] Bass\n - [ ] Mario Jumping / Hitting a block\n- [ ] Print View\n\nAudio Engine v2\n---------------\n\n- Export More Formats\n - [ ] ++ MIDI\n - [ ] .mpc file\n- [ ] Pattern Palette\n - [ ] Click and drag to extend painting notes\n- [ ] ++++ Note Duration\n- [ ] Dynamics\n - [ ] Effects\n - [ ] Volume\n - [ ] ADSR\n\nBugs\n----\n"
},
"data/demo.json": {
"path": "data/demo.json",
"content": "{\"channels\":[{\"data\":{\"0\":0,\"8\":3,\"16\":0,\"24\":4,\"32\":1,\"40\":5,\"48\":8,\"60\":9,\"64\":8,\"76\":6}},{\"data\":{\"0\":2,\"4\":2,\"8\":2,\"12\":2,\"16\":2,\"20\":2,\"24\":2,\"28\":2,\"48\":2,\"52\":2,\"56\":2,\"64\":2,\"68\":2,\"72\":2,\"76\":2}},{\"data\":{}},{\"data\":{}}],\"patterns\":[{\"beats\":8,\"notes\":[[0,10,0],[0,7,0],[3.25,10,0],[3.75,10,0],[4,9,0],[4,5,0],[4.75,5,0],[7,5,0],[7.5,7,0],[3.5,12,0],[7,-4,0],[7.5,-2,0]]},{\"beats\":8,\"notes\":[[0,5,0],[0.5,5,0],[0.5,3,0],[1,2,0],[1,5,0],[1.5,0,0],[1.5,5,0],[2,7,0],[2.5,8,0],[3,7,0],[3.5,5,0],[2,-1,0],[2.5,-1,0],[3,0,0],[3.5,2,0],[0,-9,2],[0.5,-9,2],[1,-9,2],[1.5,-9,2],[1.75,-5,2],[2.25,-5,2],[3.5,-5,2],[3,-5,2],[2.75,-5,2],[4,0,0],[4,3,0],[5,2,0],[5,5,0],[6,3,0],[6,7,0],[4,0,2],[4.5,0,2],[5,0,2],[5.5,0,2],[5.75,-2,2],[6.25,-2,2],[6.75,-2,2],[7,-2,2],[7.5,-2,2],[6.75,10,0],[6.75,7,0]]},{\"beats\":4,\"notes\":[[0,-9,2],[0.5,-9,2],[1,-9,2],[1.75,-9,2],[2.25,-9,2],[2.75,-9,2],[3,-9,2],[3.5,-9,2]]},{\"beats\":8,\"notes\":[[0,-1,0],[0,8,0],[3.25,8,0],[3.5,10,0],[3.75,8,0],[4,3,0],[4,7,0],[4.75,7,0],[4.75,15,0]]},{\"beats\":8,\"notes\":[[0,-1,0],[0,8,0],[3.25,8,0],[3.25,-1,0],[3.5,7,0],[3.5,-2,0],[3.75,5,0],[3.75,-4,0],[4,3,0],[4,-5,0]]},{\"beats\":8,\"notes\":[[0,12,0],[0.5,15,0],[0.5,10,0],[1,9,0],[1,15,0],[1.5,15,0],[1.5,7,0],[2,6,0],[2,14,0],[2.5,6,0],[3,7,0],[3.5,9,0],[3.5,12,0],[3,11,0],[2.5,12,0],[0,-3,2],[0.5,-3,2],[1,-3,2],[1.5,-3,2],[1.75,2,2],[2.25,2,2],[2.75,2,2],[3,2,2],[3.5,2,2],[4,-5,2],[4.5,-5,2],[5,-3,2],[5.5,-3,2],[6,-1,2],[6.5,-1,2],[7.25,-2,2],[7.25,2,0],[7.25,8,0],[7.25,10,0],[4,11,0],[4,14,0],[5,12,0],[5,15,0],[6,14,0],[6,17,0]]},{\"beats\":4,\"notes\":[[0,7,0],[0,-2,0],[0.25,5,0],[0.25,-4,0],[0.5,3,0],[0.5,-6,0]]},{\"beats\":4,\"notes\":[]},{\"beats\":12,\"notes\":[[0.5,12,0],[1.25,10,0],[1.25,19,0],[2,15,0],[2,7,0],[2.75,10,0],[2.75,3,0],[4,9,0],[4.5,9,0],[5,9,0],[5.5,9,0],[5.75,9,0],[5.75,17,0],[7,5,0],[7.5,7,0],[8,8,0],[4,5,0],[8.5,11,0],[8.5,15,0],[9.25,11,0],[9.25,8,0],[10,8,0],[10,-1,0],[10.75,3,0],[10.75,-4,0],[11.5,-1,0],[11.5,5,0]]},{\"beats\":4,\"notes\":[[0,-2,0],[0,7,0],[0,-9,2],[0.5,-9,2],[1,-9,2],[1.5,-9,2],[1.75,-2,2],[2.25,-2,2],[2.75,-2,2],[3,-2,2],[3.5,-2,2]]}],\"tempo\":\"120\"}",
"mode": "100644",
"type": "blob"
},
"lib/audio-context.coffee": {
"content": "AudioContext = window.AudioContext or window.webkitAudioContext\n\nmodule.exports = new AudioContext\n sampleRate: 44100\n"
},
"main.coffee": {
"content": "require \"./lib/extensions\"\n\nglobal.player = require(\"./player\")()\n\n# Apply SystemClient UI Style\n{ util } = system\nutil.applyStyle system.ui.Style.all, \"system\"\n# Add our style after player so we can override the base UI styles\nutil.applyStyle require(\"./style\"), \"app\"\n\ndocument.body.appendChild player.element\n\n# Remove right click context menu from canvases\nArray::forEach.call document.querySelectorAll('canvas'), (element) ->\n element.oncontextmenu = (e) -> e.preventDefault()\n\n# Preload Box Art Image\nimg = new Image\nimg.src = \"assets/box-art.jpg\"\n\n# Service Worker\n# TODO: this probably needs to be added to the HTML prometheus publishes\ntry\n # Can't register in blob urls or other places where there is no host\n # Don't register in Electron\n if location.host and !global.ELECTRON_VERSION\n navigator.serviceWorker.register('./sw.js')\ncatch e\n console.warn e.message\n\n# Display purchase prompt every 15 minutes on free version\nsetInterval ->\n unless player.purchased()\n player.purchase()\n, 1000 * 60 * 15\n\n# Electron App Exit Button and purchased state\nif global.ELECTRON_VERSION\n ButtonTemplate = require \"./templates/button\"\n {remote} = global.require(\"electron\")\n\n # Set purchased state\n player.purchased(true)\n\n # Override beforeUnload for the electron world\n player.removeUnloadHandler()\n closing = false\n window.addEventListener \"beforeunload\", (e) ->\n if closing\n return\n\n player.confirmUnsaved()\n .then ->\n closing = true\n remote.getCurrentWindow().close()\n\n return e.returnValue = true\n\n player.element.querySelector('section.buttons').appendChild ButtonTemplate\n text: \"🚪 Exit\"\n class: \"exit\"\n click: ->\n remote.getCurrentWindow().close()\n"
},
"persistence.coffee": {
"content": "Gist = require \"./lib/legacy/gist\"\nDrop = require \"./lib/drop\"\nReadFile = require \"./lib/read-file\"\nMidi2Composer = require \"./lib/midi2composer\"\n\nPublishTemplate = require \"./templates/publish\"\nclipboardCopy = require \"./lib/clipboard-copy\"\n\n{Modal, Progress} = system.ui\n\npublishView = Progress\n message: \"Publishing...\"\n\nmodule.exports = (I={}, self) ->\n # Autoload from location hash\n defaults I,\n unsaved: false\n\n self.attrAccessor \"unsaved\"\n\n jsonFromFile = (file) ->\n if file.type.match /^audio\\/mid/\n if self.purchased()\n file.readAsArrayBuffer()\n .then (buffer) ->\n Midi2Composer new Uint8Array(buffer)\n else\n # Display Purchase Modal\n self.purchase()\n Promise.reject(\"Only full version can import MIDI\")\n else\n file.readAsJSON()\n\n Drop document.documentElement, (files) ->\n Promise.all Array::map.call files, (file) ->\n jsonFromFile(file)\n .catch -> null\n .then (readFiles) ->\n json = readFiles.filter((f) -> !!f)[0]\n\n if json\n self.fromJSON(json)\n\n fileInput = ReadFile\n accept: \".dxc,.mid,audio/mid,.midi,audio/midi\"\n change: (file) ->\n jsonFromFile(file)\n .then self.fromJSON\n .then ->\n Modal.hide()\n .catch console.error\n\n fileInput.style.display = \"none\"\n document.body.appendChild fileInput\n\n # NOTE: Track `prompted` so in an iframe it won't trigger twice\n prompted = false\n unloadHandler = (e) ->\n return if prompted\n prompted = true\n setTimeout ->\n prompted = false\n\n if self.unsaved()\n e.returnValue = \"Your changes haven't yet been saved. If you leave now you will lose your work.\"\n\n return e.returnValue\n window.addEventListener \"beforeunload\", unloadHandler\n\n setTimeout ->\n if hash = location.hash\n self.loadFromSlug hash.substr(1)\n , 0\n\n self.extend\n saveAs: ->\n Modal.prompt \"Name\"\n .then (name) ->\n return unless name\n data = self.song().toJSON()\n localStorage[\"songs_#{name}\"] = JSON.stringify data\n self.unsaved false\n\n saveFile: ->\n Modal.prompt \"Name\", \"untitled\", \"Save Song\"\n .then (name) ->\n return unless name\n\n blob = new Blob [JSON.stringify self.song().toJSON()],\n type: \"application/json\"\n\n blob.download(\"#{name}.dxc\")\n self.unsaved false\n\n openFile: ->\n fileInput.click()\n\n loadFromSlug: (slug) ->\n Promise.resolve()\n .then ->\n if slug.match /^api-/\n self.loadAPIData(slug.substr(4))\n else if slug\n self.loadGist slug\n\n loadURL: (url) ->\n fetch(url)\n .then (result) ->\n result.json()\n .then self.fromJSON\n\n loadFromURLString: ->\n Modal.prompt \"Paste shareable URL here:\", \"https://danielx.net/composer/#api-4IuXACKoqtSke9jTAThC5I_eWc4me53ze-1cri-L8kE\", \"Load from URL\"\n .then (url) ->\n return unless url\n if url.indexOf(\"#\")\n [_, slug] = url.split(\"#\")\n self.loadFromSlug(slug)\n else\n self.loadURL(url)\n\n loadDemo: ->\n self.confirmUnsaved()\n .then ->\n Modal.hide()\n self.fromJSON(require(\"./data/demo\"))\n\n loadLocalStorage: ->\n Modal.prompt \"Name\"\n .then (name) ->\n return unless name\n\n if name\n self.fromJSON JSON.parse localStorage[\"songs_#{name}\"] \n\n getState: ->\n JSON.parse JSON.stringify self.song().toJSON()\n\n restoreState: (data) ->\n self.song().fromJSON(JSON.parse JSON.stringify data)\n self.rerenderNotes()\n\n # Returns a promise that resolves if there are no unsaved changes or the\n # the user confirms they are willing to lose unsaved changes.\n confirmUnsaved: ->\n Promise.resolve()\n .then ->\n if self.unsaved()\n Modal.confirm \"If you continue your changes will be lost!\", \"Unsaved changes!\"\n .then (confirmed) ->\n return Promise.reject() unless confirmed\n\n fromJSON: (data) ->\n self.confirmUnsaved()\n .then ->\n self._fromJSON(data)\n\n _fromJSON: (data) ->\n self.song().fromJSON(data)\n\n self.reset()\n self.rerenderNotes()\n\n removeUnloadHandler: ->\n window.removeEventListener \"beforeunload\", unloadHandler\n\n publish: ->\n Modal.show publishView.element,\n cancellable: false\n\n fetch \"https://api.danielx.net/composer\",\n method: 'POST'\n body: JSON.stringify(self.song())\n headers:\n 'Content-Type': 'text/plain'\n .then (res) -> res.json()\n .then (id) ->\n url = \"https://danielx.net/composer/#api-#{id}\"\n\n try\n history.replaceState({}, \"\", url)\n catch e\n console.warn e\n\n Modal.show PublishTemplate(\n value: url\n status: Observable \"\"\n copy: ->\n clipboardCopy(url)\n @status \"Copied!\"\n done: ->\n Modal.hide()\n ),\n cancellable: false\n .catch (e) ->\n console.warn e\n Modal.hide()\n\n loadAPIData: (id) ->\n fetch \"https://api.danielx.net/composer/data/#{id}\",\n method: 'GET'\n headers:\n 'Content-Type': 'text/plain'\n .then (res) -> res.json()\n .then (data) ->\n self.fromJSON(data)\n location.hash = \"api-\" + id\n\n # Load legacy format songs from when we could save them as anonymous Github\n # gists.\n loadGist: (id) ->\n Gist.load(id).then (data) ->\n self.fromJSON(data)\n location.hash = id\n , ->\n Modal.alert \"Couldn't load gist with id: #{id}\"\n\n loadGistPrompt: ->\n if id = prompt \"Gist id\", location.hash.substr(1) || \"0b4c4656a6eb1d246829\"\n self.loadGist(id)\n\n"
},
"pixie.cson": {
"content": "title: \"Mario Paint Music Composer - danielx.net\"\ndescription: \"\"\"\nThis Mario Paint inspired composer tool is easy and fun. You can create simple\nand beautiful songs right in your browser and share them with the world!\n\"\"\"\niconURL: \"assets/images/raccoon.png\"\nversion: \"0.4.7\"\npublishPath: \"/My Briefcase/public/danielx.net/\"\nname: \"composer\"\ndependencies:\n \"!system\": \"https://danielx.net/system/0.5.0-pre.4.json\"\nremoteDependencies: [\n \"https://js.stripe.com/v3/\"\n]\nwidth: 1024\nheight: 960\nsandbox: \"allow-same-origin allow-modals allow-forms allow-pointer-lock allow-popups allow-scripts allow-popups-to-escape-sandbox midi\"\ncognito: # Whimsy.space 'My Briefcase'\n identityPoolId: 'us-east-1:4fe22da5-bb5e-4a78-a260-74ae0a140bf9'\n poolData:\n UserPoolId : 'us-east-1_cfvrlBLXG'\n ClientId : '3fd84r6idec9iork4e9l43mp61'\n bucket: \"whimsy-fs\"\nmanifest:\n name: \"DanielX.net Paint Composer\"\n short_name: \"Composer\"\n display: \"standalone\"\n start_url: \".\"\n scope: \"./\"\n icons: [ {\n \"src\": \"assets/images/raccoon.png\",\n \"sizes\": \"48x48\"\n\t}, {\n \"src\": \"https://danielx.net/composer/icon256.png\",\n \"sizes\": \"256x256\"\n\t} ]\n"
},
"player-audio.coffee": {
"content": "###\nPlayer Audio\n============\n\nMain audio loop\n\nNeeds tempo, playable, start beat, end beat, looping mode to play.\n\nProvides playTime and playing methods.\n###\n\n{Progress, Modal, Style} = system.ui\n\n# Safari uses webkit prefix.\nOfflineAudioContext = window.OfflineAudioContext or window.webkitOfflineAudioContext\n\nliveContext = require(\"./lib/audio-context\")\nEncoder = require \"./lib/encoder/index\"\n{quantize, staffNoteToPitch} = require \"./lib/util\"\n{limiter, stereoAnalyser} = FX = require(\"./lib/fx\")\n\nFXNetwork = (destination) ->\n {context} = destination\n gain = context.createGain()\n gain.connect destination\n\n fx = FX.choices.reduce (o, name) ->\n o[name] = FX[name] gain\n return o\n , {}\n\n default: gain\n fx: fx\n dispose: ->\n {currentTime} = context\n gain.gain.exponentialRampToValueAtTime 0.001, 0.1 + currentTime\n gain.gain.setValueAtTime 0, 0.1 + currentTime\n\n setTimeout ->\n gain.disconnect()\n , 0.25\n\nmodule.exports = (I, self) ->\n playTime = 0 # beats\n secondsPerMinute = 60 # unitless\n lastContextTime = 0\n lastQueuedBeat = 0\n lookahead = 0.1 # seconds\n toBeat = null\n wrapping = false\n\n self.attrObservable \"playing\"\n\n # The destination for 'live audio' goes to the analyser node then the speakers.\n # We add a hard limiter on right before the analyser and output.\n self.analysedDestination = analysedDestination = stereoAnalyser liveContext.destination\n liveDestination = limiter analysedDestination\n\n # FX Networks are disposable so we can fade out when stopping / restarting\n activeDestination = FXNetwork(liveDestination)\n\n # Need more lookahead to play when window loses focus and request animation frame\n # gets sparser\n document.addEventListener \"visibilitychange\", (e) ->\n if document.hidden\n lookahead = 1.25\n else\n lookahead = 0.1\n bufferAudio()\n\n initPlay = ->\n {context} = liveDestination\n\n context.resume()\n lastContextTime = context.currentTime\n lastQueuedBeat = playTime\n wrapping = false\n\n # playTime in beats\n # elapsedSeconds in seconds\n computeElapsedBeats = (playTime, elapsedSeconds) ->\n if playTime >= self.length()\n playTime -= self.length()\n\n [section, beatsLeft] = self.sectionAt(playTime)\n _bps = section.tempo() / 60\n\n beats = elapsedSeconds * _bps\n\n if beats > beatsLeft\n beatsLeft + computeElapsedBeats playTime + beatsLeft, elapsedSeconds - beatsLeft / _bps\n else\n beats\n\n # Schedules upcoming sounds to play\n playUpcomingSounds = (currentBeat, fromBeat, toBeat) ->\n dt = toBeat - fromBeat\n return if dt <=0\n\n self.upcomingNotes fromBeat, dt, ([beat, staffNote, accidental, instrument], section, s) ->\n pitch = staffNoteToPitch(staffNote, accidental, section.keySignature())\n\n # Play in FX destination for section\n self.playNote instrument, pitch, s, activeDestination.fx[section.presetName()]\n , currentBeat\n\n # beats per second\n bps = ->\n self.tempo() / secondsPerMinute\n\n # Buffer audio to the output device\n # The challenge is that the playhead needs to accurately track where we are in\n # beats, according to the context currentTime, but we need to buffer ahead so\n # we can properly time all the upcoming notes.\n #\n # There is one more tricky part where we wrap around. Even when not wrapping\n # the bufferd region shouldn't grow but the playhead should still advance\n # until done.\n #\n # Multi-section makes this even more tricky!\n bufferAudio = ->\n if self.playing()\n length = self.length()\n return if length < 1 or isNaN(length)\n\n {context} = liveDestination\n {currentTime} = context\n\n dt = currentTime - lastContextTime # seconds\n return if dt is 0\n lastContextTime = currentTime\n\n elapsedBeats = computeElapsedBeats playTime, dt\n self.animateNoteElements(playTime, elapsedBeats)\n playTime += elapsedBeats\n\n toBeat = playTime + computeElapsedBeats playTime, lookahead\n\n # We're continuing to wrap around until playTime loops\n if wrapping\n toBeat -= length\n playUpcomingSounds(playTime - length, lastQueuedBeat, toBeat)\n\n else\n # Enqueue sounds within the upcoming window since last queued beat\n playUpcomingSounds(playTime, lastQueuedBeat, toBeat)\n\n if toBeat >= length and self.loop()\n wrapping = true\n toBeat -= length\n lastQueuedBeat = 0\n playUpcomingSounds(playTime - length, lastQueuedBeat, toBeat)\n\n # Sometimes the toBeat could be less (we reduce the lookahead when focusing\n # the window, so only adjust it forward so we don't double buffer notes.\n lastQueuedBeat = Math.max lastQueuedBeat, toBeat\n\n if playTime >= length\n # Once playtime catches up to the end we're no longer wrapping\n wrapping = false\n if self.loop()\n playTime -= length\n self.animateNoteElements(0, playTime)\n else\n playTime = 0\n self.playing false\n\n # Set active section based on playTime\n self.activeSection self.sectionAt(playTime)[0]\n\n return\n\n setInterval ->\n bufferAudio()\n , 1000 / 120\n\n animBuffer = ->\n requestAnimationFrame animBuffer\n bufferAudio()\n animBuffer()\n\n self.extend\n exportSong: (song, opts={}) ->\n {name, type} = opts\n\n name ?= \"song\"\n type ?= \"mp3\"\n\n extension = \".#{type}\"\n\n progressView = Progress\n message: \"Rendering Audio...\"\n\n Modal.show progressView.element,\n cancellable: false\n\n cleanup = ->\n Modal.hide()\n\n err = (fn) ->\n (e) ->\n fn()\n throw e\n\n # Length total of all sections in beats\n songLength = song.length()\n\n audioChannels = 2\n samplesPerSecond = 44100\n # Export duration trims empty measures on last section\n lengthInSeconds = song.exportDuration()\n offlineContext = new OfflineAudioContext(audioChannels, samplesPerSecond * lengthInSeconds, samplesPerSecond)\n offlineFxNetwork = FXNetwork limiter offlineContext.destination\n\n new Promise (resolve, reject) ->\n t = 0 # beats\n dt = 1 # beats\n\n work = ->\n song.upcomingNotes t, dt, ([beat, staffNote, accidental, instrument], section, s) ->\n presetName = section.presetName()\n keySig = section.keySignature()\n note = staffNoteToPitch(staffNote, accidental, keySig)\n\n self.playNote instrument, note, s, offlineFxNetwork.fx[presetName]\n , 0\n\n t += dt\n\n if t <= songLength\n setTimeout work, 0\n else\n p = offlineContext.startRendering()\n\n if p\n p.then(resolve, reject)\n else\n # Safari doesn't support the Promise version yet\n offlineContext.oncomplete = ({renderedBuffer}) ->\n resolve renderedBuffer\n # There is no `onerror` event\n\n return\n\n work()\n return\n\n .then (audioBuffer) ->\n if type is \"mp3\"\n progressView.message \"Encoding mp3...\"\n Encoder.audioBufferToMP3(audioBuffer)\n else\n progressView.message \"Encoding wav...\"\n Encoder.audioBufferToWav(audioBuffer)\n .then (blob) ->\n blob.download name + extension\n .then cleanup, err cleanup\n\n # TODO: Think about how to connect these note events into audio networks fx, etc.\n # Schedule a note to be played, use the buffer at the given index, pitch shift by\n # `note` semitones, and play at `time` seconds in the future.\n playNote: (instrumentId, note=0, time=0, dest) ->\n dest ?= activeDestination.fx[self.presetName()] or liveDestination\n {context} = dest\n sample = self.samples()[instrumentId].I\n\n if sample\n {buffer, pitchShift, pan, volume} = sample\n note += pitchShift\n\n # Add panner node, this also forces mono-samples to stereo\n # FUTURE: iOS doesn't support createStereoPanner yet\n panner = context.createPanner()\n panner.panningModel = 'equalpower'\n panner.setPosition(pan, 0, 1 - Math.abs(pan))\n\n gain = context.createGain()\n gain.gain.value = volume\n\n rate = Math.pow 2, note / 12\n source = self.playBuffer(buffer, rate, time, panner)\n\n panner.connect gain\n gain.connect dest\n\n source.onended = ->\n gain.disconnect()\n\n # Plays a buffer directly into the live audio context destination or the\n # given destination. Rate and time are given to shift pitch and start time.\n # time is in seconds relative to the currentTime of the destination's\n # context.\n #\n # This is used for the 'eraser' sound effect.\n # time is in seconds, must be >= 0\n playBuffer: (buffer, rate=1, time=0, dest=liveDestination) ->\n {context} = dest\n\n # sometimes time comes in very slightly negative o_o, maybe a rounding error?\n # Audio context throws an error if start is called with a negative argument\n unless time >= 0\n time = 0\n\n source = context.createBufferSource()\n source.buffer = buffer\n source.connect(dest or context.destination)\n source.start(time + context.currentTime)\n source.playbackRate.value = rate\n\n return source\n\n # Adjust playhead by dt beats, centering the scroll on it quantized to `q`\n adjustPlayhead: (dt, q=0) ->\n self.setPlayHead quantize playTime + dt, q\n self.recenterPlayhead()\n\n # Sets playhead cursor to the `t` in beats, clamping to song length\n # t in beats\n setPlayHead: (t) ->\n return if self.playing()\n\n # Needs to be < length otherwise section is undefined\n playTime = Math.min self.length() - 0.00001, Math.max t, 0\n self.activeSection self.sectionAt(playTime)[0]\n\n return playTime\n\n bufferTo: ->\n toBeat\n \n lastQueuedBeat: ->\n lastQueuedBeat\n\n playTime: ->\n playTime\n\n playFromStart: ->\n if self.playing()\n self.stop()\n else\n playTime = 0\n self.playing true\n\n initPlay()\n\n pause: ->\n if self.playing()\n self.stop()\n else\n self.playing true\n initPlay()\n\n play: ->\n self.pause()\n\n stop: ->\n self.playing false\n # Quick fade\n activeDestination.dispose()\n activeDestination = FXNetwork(liveDestination)\n\n rewind: ->\n self.stop()\n playTime = 0\n self.scrollTo(0)\n\n reset: ->\n self.stop()\n playTime = 0\n self.activeSection self.sections()[0]\n"
},
"player.coffee": {
"content": "# Player is the `app`. It requires and includes all the components then renders\n# them into the templates.\n\nrequire \"./lib/extensions\"\n\n{Progress, Modal} = system.ui\n\nExportTemplate = require \"./templates/export\"\nOptionTemplate = require \"./templates/option\"\n\nSample = require \"./sample\"\nSong = require \"./song-v2\"\n\nStaffView = require \"./staff-view\"\n\nPattern = require \"./lib/pattern\"\nUndo = require \"./lib/undo\"\n\n{purchase} = require \"./lib/stripe-checkout\"\n\nmodule.exports = (I={}, self=Model(I)) ->\n defaults I,\n patterns: []\n\n self.include require \"./lib/hotkeys\"\n self.include require \"./lib/midi-input\"\n\n self.attrObservable \"activeSection\", \"purchased\"\n\n self.attrModels \"patterns\", Pattern\n self.attrModel \"song\", Song\n\n song = self.song()\n\n # Loading default sample pack\n Sample.loadPack()\n .then song.loadSettings\n .then ->\n self.applySampleCSS Sample.image\n\n self.extend\n notePosition: Observable \"\"\n\n samples: song.settings\n\n addNote: (note) ->\n self.unsaved true\n self.song().addNote note\n self.pushState self.getState()\n\n removeNote: (note, nearby) ->\n self.unsaved true\n removed = self.song().removeNote note, nearby\n self.pushState self.getState()\n \n return removed\n\n addPattern: (beatNote, pattern) ->\n self.unsaved true\n self.song().addPattern beatNote, pattern\n self.pushState self.getState()\n self.triggerRerender()\n\n deleteRange: (range) ->\n self.unsaved true\n self.song().deleteRange range\n\n self.pushState self.getState()\n self.triggerRerender()\n\n copyRange: (range) ->\n pattern = self.song().copyRange range\n\n if pattern.notes().length\n self.activeToolIndex 3 # Pattern palette\n # Create and select pattern\n self.activePatternIndex self.patterns.push(pattern) - 1\n\n moveNotes: (notes, beatDelta, staffDelta) ->\n self.unsaved true\n\n # Adjust every note\n notes.forEach (note) ->\n note[0] += beatDelta\n note[1] += staffDelta\n\n # Notes need to get re-sectioned if they cross a section boundary!\n if beatDelta != 0\n self.song().resection(notes)\n\n self.pushState self.getState()\n self.triggerRerender(true)\n\n exportSprites: ->\n Sample.exportPNG(self.samples())\n\n # Create a style element containing the background images for notes from a\n # list of samples. Applies background image when the class of the note is\n # the sample name.\n applySampleCSS: (image) ->\n style = document.querySelector \"style.samples\"\n style ?= document.createElement \"style\"\n style.classList.add \"samples\"\n n = image.width / 48\n style.innerHTML = [0...n].map (i) ->\n x = -i * 48\n \"\"\"\n note.i#{i}:after {background: url(#{image.src}) #{x}px 0;}\n note.i#{i}.active:after {background: url(#{image.src}) #{x}px 48px;}\n tools > .i#{i} {background: url(#{image.src}) #{x}px 0px;}\n tools > .i#{i}.active {background: #FFC107 url(#{image.src}) #{x}px 48px;}\n \"\"\"\n .join(\"\\n\")\n document.head.appendChild(style)\n\n loop: song.loop\n toggleLoop: ->\n self.loop.toggle()\n loopButtonClass: ->\n \"active\" if self.loop()\n\n playButtonClass: ->\n \"active\" if self.playing()\n\n # Delegating to song\n length: song.length\n sections: song.sections\n sectionAt: song.sectionAt\n\n # Delegate to active section\n tempo: ->\n self.activeSection().tempo.apply(null, arguments)\n presetName: ->\n self.activeSection().presetName.apply(null, arguments)\n keySignature: ->\n self.activeSection().keySignature.apply(null, arguments)\n timeSignature: ->\n self.activeSection().timeSignature.apply(null, arguments)\n\n # TODO: Clear song vs clear section\n clearDisabled: ->\n !self.activeSection().notes().length\n\n clear: ->\n Modal.confirm \"Clear entire song?\"\n .then (confirmed) ->\n if confirmed\n song.clear()\n self.unsaved true\n self.pushState self.getState()\n self.triggerRerender()\n\n about: ->\n Modal.show aboutTemplate,\n cancellable: true\n\n showHelp: ->\n Modal.show helpTemplate,\n cancellable: true\n\n showPersistenceModal: ->\n Modal.show persistenceElement\n\n hiddenIfPurchased: ->\n \"hidden\" if self.purchased()\n\n hiddenUnlessPurchased: ->\n \"hidden\" unless self.purchased()\n\n purchase: ->\n Modal.show buyNowTemplate,\n cancellable: true\n\n discord: ->\n window.open \"https://discord.gg/wcpWDNk\", \"_blank\"\n\n oldVersion: ->\n window.location = \"https://danielx.net/composer/0.2.0-pre.0/\"\n\n exportAudio: ->\n name = Observable \"song\"\n selectedType = Observable \"mp3\"\n formatTypes = [\"mp3\", \"wav\"]\n\n Modal.show ExportTemplate\n name: name\n title: generateExportTitle()\n selectedType: selectedType\n formatOptionElements: ->\n formatTypes.map (type) ->\n OptionTemplate\n text: type\n value: type\n\n cancel: (e) ->\n e.preventDefault()\n Modal.hide()\n submit: (e) ->\n e.preventDefault()\n\n self.exportSong self.song(),\n name: name()\n type: selectedType()\n\n upcomingNotes: song.upcomingNotes\n\n fullscreen: Observable document.fullscreenElement\n\n loadFromURL: (url) ->\n progressView = Progress\n value: 0\n message: \"Loading...\"\n\n Modal.show progressView.element,\n cancellable: false\n\n ajax.ajax\n url: url\n responseType: \"json\"\n .progress ({lengthComputable, loaded, total}) ->\n if lengthComputable\n progressView.value loaded / total\n .then self.fromJSON\n .then ->\n Modal.hide()\n .catch (e) ->\n if e.statusText\n Modal.alert \"An error has occurred: #{e.status} - #{e.statusText}\"\n else\n Modal.alert \"An error has occurred: #{e.message}\"\n\n # This is just copied from the README, update there, it is the canonical source\n hotkeysTableElement: ->\n div = document.createElement 'div'\n div.innerHTML = \"\"\"\n <table>\n <thead>\n <tr>\n <th>Action</th>\n <th>Key</th>\n </tr>\n </thead>\n <tbody><tr>\n <td>Select Instrument</td>\n <td><code>0-9</code></td>\n </tr>\n <tr>\n <td>Select Instrument</td>\n <td><code>&lt;backtick&gt; 1-7</code></td>\n </tr>\n <tr>\n <td>Select Pattern</td>\n <td><code>Shift+0-9</code></td>\n </tr>\n <tr>\n <td>Eraser Tool</td>\n <td><code>e</code></td>\n </tr>\n <tr>\n <td>Selection Tool</td>\n <td><code>s</code></td>\n </tr>\n <tr>\n <td>Undo</td>\n <td><code>Ctrl+z</code>, <code>⌘+z</code></td>\n </tr>\n <tr>\n <td>Redo</td>\n <td><code>Ctrl+y</code>, <code>⌘+y</code></td>\n </tr>\n <tr>\n <td>Play/Pause</td>\n <td><code>Space</code></td>\n </tr>\n <tr>\n <td>Play from Beginning</td>\n <td><code>Enter</code></td>\n </tr>\n <tr>\n <td>&nbsp;</td>\n <td></td>\n </tr>\n <tr>\n <td><strong>Selection</strong></td>\n <td></td>\n </tr>\n <tr>\n <td>Copy</td>\n <td><code>c</code></td>\n </tr>\n <tr>\n <td>Cut</td>\n <td><code>x</code></td>\n </tr>\n <tr>\n <td>Delete</td>\n <td><code>Delete</code></td>\n </tr>\n <tr>\n <td>Reset</td>\n <td><code>Esc</code></td>\n </tr>\n <tr>\n <td>&nbsp;</td>\n <td></td>\n </tr>\n <tr>\n <td><strong>Data</strong></td>\n <td></td>\n </tr>\n <tr>\n <td>Save</td>\n <td><code>Ctrl+s</code>, <code>⌘+s</code></td>\n </tr>\n <tr>\n <td>Open</td>\n <td><code>Ctrl+o</code>, <code>⌘+o</code></td>\n </tr>\n <tr>\n <td>Export</td>\n <td><code>Ctrl+r</code>, <code>⌘+r</code></td>\n </tr>\n <tr>\n <td>&nbsp;</td>\n <td></td>\n </tr>\n <tr>\n <td><strong>Meta</strong></td>\n <td></td>\n </tr>\n <tr>\n <td>About</td>\n <td><code>F1</code></td>\n </tr>\n <tr>\n <td>Toggle Full screen</td>\n <td><code>F11</code></td>\n </tr>\n <tr>\n <td>Help</td>\n <td><code>?</code></td>\n </tr>\n </tbody></table>\n \"\"\"\n return div.children[0]\n\n self.include require \"./player-audio\"\n self.include require \"./persistence\"\n self.include require \"./tools\"\n self.include StaffView\n\n Undo(self)\n self.pushState self.getState()\n\n # Init active section\n self.activeSection self.sections()[0]\n\n # This common convention passes self as a model to a sub-view and attaches the\n # view's element to be rendered in our templates.\n self.noteControlElement = require(\"./views/note-control\")(self).element\n\n # Analyser View\n # stereoAnalyserView = require(\"./views/stereo-analyser\")(context)\n # self.stereoAnalyserElement = stereoAnalyserView.element\n\n # Meter Picker\n MeterPicker = require(\"./views/meter-picker\")\n self.meterPickerElement = MeterPicker(self).element\n\n # FX Picker\n FXPicker = require(\"./views/fx-picker\")\n self.fxPickerElement = FXPicker({\n presetName: self.presetName\n }).element\n\n # TODO: Make fx bg part of section\n # Update background style and fx preset when the chosen preset changes\n Observable ->\n name = self.presetName()\n self.fxStyle FXPicker.styleFor(name)\n\n # Each section display its own meter background based on the time signature\n # The classes are n2 - n7 based on the numerator of the time signature\n require(\"./lib/meter-background\")\n .then (timeSignatureMeterBackgroundUrls) ->\n style = document.createElement \"style\"\n style.classList.add \"meter\"\n style.innerHTML = Object.keys(timeSignatureMeterBackgroundUrls).map (n) ->\n url = timeSignatureMeterBackgroundUrls[n]\n \"\"\"\n song-section.n#{n} {background-image: url(#{url});}\n \"\"\"\n .join(\"\\n\")\n document.head.appendChild(style)\n\n # Demo song picker\n require(\"./views/demo-picker\")(self)\n\n # Sample / Sprite Config\n require(\"./views/settings\")(self)\n\n # Arranger\n require(\"./views/arranger\")(self)\n\n Template = require(\"./templates/app\")\n self.element = Template\n actionsElement: require(\"./views/actions\")(self).element\n toolsElement: require(\"./views/tools\")(self).element\n patternsElement: require(\"./views/patterns\")(self).element\n staffElement: self.staffElement\n notePosition: self.notePosition\n\n aboutTemplate = require(\"./templates/about\")(self)\n helpTemplate = require(\"./templates/help\")(self)\n buyNowTemplate = require(\"./templates/buy-now\")\n submit: (e) ->\n e.preventDefault()\n purchase()\n\n persistenceElement = require(\"./templates/persistence\")(self)\n\n animate ->\n self.performDraw()\n\n # Rerender on length changes\n Observable ->\n song.length()\n self.rerenderNotes()\n\n return self\n\nrand = (a) ->\n a[Math.floor Math.random() * a.length]\n\ngenerateExportTitle = ->\n adjective = rand [\n \"cool\"\n \"rad\"\n \"kickin'\"\n \"bumpin'\"\n \"sweet\"\n \"tasty\"\n ]\n\n noun = rand [\n \"banger\"\n \"track\"\n \"song\"\n \"tune\"\n \"jam\"\n ]\n\n \"Export your #{adjective} #{noun}\"\n\nanimate = (fn) ->\n step = ->\n requestAnimationFrame(step)\n fn()\n\n step()\n"
},
"sample.coffee": {
"content": "context = require \"./lib/audio-context\"\n\nbufferLoader = (url) ->\n new Promise (resolve, reject) ->\n fetch(url)\n .then (response) ->\n throw response unless response.ok\n response.arrayBuffer()\n .then (buffer) ->\n context.decodeAudioData buffer, resolve, reject\n\nblobLoader = (url) ->\n fetch(url)\n .then (response) ->\n throw response unless response.ok\n response.blob()\n\nBASE_PATH = window.location\nurlFor = (path) ->\n \"#{BASE_PATH}assets/#{path}\"\n\nimgLoader = (url) ->\n new Promise (resolve, reject) ->\n image = new Image\n image.crossOrigin = true\n image.src = url\n image.onload = ->\n resolve image\n image.onerror = reject\n\nmodule.exports = Sample =\n load: (data) ->\n {sample} = data\n\n # Load audio buffer\n bufferLoader(urlFor(sample))\n .then (buffer) ->\n Object.assign data,\n buffer: buffer\n\n loadPack: ->\n imgLoader urlFor \"images/default.png\"\n .then (img) ->\n # TODO: a cleaner way to pass this to applySampleCSS\n Sample.image = img\n\n Promise.all defaultPack.map (sample, i) ->\n # Get blob url\n canvas = document.createElement 'canvas'\n canvas.width = 48\n canvas.height = 48\n ctx = canvas.getContext('2d')\n\n ctx.drawImage(img, i * 48, 0, 48, 48, 0, 0, 48, 48)\n\n new Promise (resolve, reject) ->\n canvas.toBlob (blob) ->\n url = URL.createObjectURL blob\n sample.cursor = \"url(#{url}) #{24} #{24}, default\"\n sample.url = url\n resolve()\n\n .then ->\n Promise.all(defaultPack.map(Sample.load))\n\n # Export all image/activeImage sprites to a single png\n # This is going to be part of the future format\n exportPNG: (samples) ->\n canvas = document.createElement 'canvas'\n canvas.width = 48 * samples.length\n canvas.height = 96\n ctx = canvas.getContext('2d')\n\n # All images are 48x48\n samples.forEach ({image, activeImage}, i) ->\n x = 48 * i\n ctx.drawImage(image, x, 0) #normal\n ctx.drawImage(activeImage, x, 48) # active y=48\n\n canvas.toBlob (blob) ->\n url = URL.createObjectURL(blob)\n window.open url, \"_blank\"\n\n# Load Editor sound effects, currently just the eraser\nbufferLoader(\"#{BASE_PATH}assets/erase2.wav\")\n.then (buffer) ->\n Sample.fx =\n eraser: buffer\n\nsamples =\n synth:\n sample: \"synth.wav\"\n piano:\n sample: \"piano.wav\"\n pan: -0.5\n guitar:\n sample: \"guitar.wav\"\n pan: 0.25\n bass:\n sample: \"16.wav\"\n horn:\n sample: \"horn.wav\"\n pan: -0.333\n orch_hit:\n sample: \"orch_hit.wav\"\n pan: 0.25\n chime:\n sample: \"5.wav\"\n pan: 0.5\n organ:\n sample: \"organ.wav\"\n pan: -0.25\n drum:\n sample: \"drum.wav\"\n snare:\n sample: \"snare.wav\"\n woodblock:\n sample: \"14.wav\"\n pan: -0.333\n clap:\n sample: \"clap.wav\"\n hat:\n sample: \"hat.wav\"\n pan: 0.333\n baby:\n sample: \"baby.wav\"\n pan: -0.5\n yoshi:\n sample: \"yoshi.wav\"\n pan: 0.5\n pig:\n sample: \"oink.wav\"\n cat:\n sample: \"cat.wav\"\n pan: 0.25\n dog:\n sample: \"dog.wav\"\n pan: -0.25\n french:\n sample: \"french-horn.wav\"\n pan: 0.333\n pitchShift: 0\n nylon:\n sample: \"nylon-guitar2.wav\"\n pan: -0.25\n pitchShift: -12\n snare2:\n sample: \"snare2.wav\"\n pitchShift: 0\n\ndefaultPack = Object.keys(samples).map (name, i) ->\n sample = samples[name]\n sample.name = name\n sample.index = i\n sample.pitchShift ?= -5 # These samples are pitched to F, the -5 pithces them to C\n sample.pan ?= 0\n sample.volume ?= 1\n\n sample\n"
},
"style.styl": {
"content": "primary-color-dark = #4a148c\nprimary-color = #673ab7\nhighlight-color = #FFE0B2\nactive-color = #FFC107\nbg-color = #ede7f6\n\npixelated()\n -ms-interpolation-mode: nearest-neighbor\n image-rendering: crisp-edges\n image-rendering: pixelated\n\n@font-face\n font-display: auto\n font-family: 'Chicago'\n src: url('assets/fonts/chicago.woff2') format('woff2'),\n url('assets/fonts/chicago.woff') format('woff')\n font-weight: normal\n font-style: normal\n\n*\n box-sizing: border-box\n\nimg\n max-width: 100%\n\n.hidden\n display: none !important\n\n#modal \n > *\n border: 1px solid primary-color\n border-radius: 4px\n box-shadow: 1px 2px 0px primary-color\n color: inherit\n padding: 1rem\n\n > h1, > h2\n margin-top: 0\n\n > section.purchase\n background-color: transparent\n border: none\n border-radius: 0\n box-shadow: none\n padding: 0\n\n > .publish\n > p.status:empty\n margin: 0\n \n > pre\n user-select: all\n \n > actions\n display: flex\n > button:last-child\n margin-left: auto\n\n:focus\n color: white\n background-color: primary-color\n outline: none\n\nhtml, body\n height: 100%\n\nbody\n color: #241440\n display: flex\n font-family: Chicago, sans-serif\n font-size: 16px\n line-height: 1rem\n margin: 0\n overflow: hidden\n user-select: none\n\np\n font-family: sans-serif\n\n// Input and form things\ninput, textarea, select, button\n font-family: inherit\n\ninput\n background-color: bg-color\n border: 1px solid primary-color\n border-radius: 4px\n box-shadow: 1px 2px 0px primary-color inset\n color: primary-color\n font-size: inherit\n padding: 2px 0.25em\n\n // Firefox number spinners are a crime to my eyes ;_;\n // TODO Custom style for number spinners on all browsers\n &[type=\"number\"]\n -moz-appearance: textfield\n \n &:focus\n background-color: active-color\n color: rgba(0, 0, 0, 0.69)\n\n@keyframes note-active\n from\n background-color: rgba(0, 0, 0, 0)\n\n to\n background-color: rgba(255, 0, 255, 0)\n\n// Background has to be on ::after so it is above the ledger lines on ::before\n// Accidentals are in ::after content, vertically centered and left of the bg\nnote\n font-size: 48px\n height: 48px\n pixelated()\n position: absolute\n width: 48px\n\n &.active\n animation-name: note-active\n animation-duration: 0.25s\n\n &::after\n align-items: center\n background-repeat: no-repeat\n background-position: 100% 50%\n content: \"\"\n display: flex\n position: absolute\n left: 0\n top: 0\n width: 100%\n height: 100%\n text-indent: -12px\n\n &.♭\n &::after\n content: \"♭\"\n\n &.♯\n &::after\n content: \"♯\"\n\n // Extra ledger lines\n &.C4, &.A5, &.C6, &.C2, &.E2, &.B5, &.D2\n &::before\n content: \"\"\n width: 48px\n left: 0px\n top: 23px\n position: absolute\n height: 0\n border-bottom: 2px solid black\n\n &.B5\n &::before\n top: 40px\n\n &.D2\n &::before\n top: -8px\n\nsong-section\n height: 483px\n position: absolute\n top: -241px\n\n &::after\n content: \"\"\n border-right: 2px solid black\n height: 100%\n position: absolute\n right: 10px\n\n > span.measure-number\n background-color: rgba(255, 255, 255, 0.9375)\n border: 1px solid black\n box-shadow: 1px 1px 0 0 rgba(0,0,0,0.5)\n font-style: italic\n left: 0px\n padding: 2px 6px 2px 4px\n position: absolute\n top: -132px\n\n > div.key-signature\n &.s > ::after\n content: \"♯\"\n\n &.f > ::after\n content: \"♭\"\n\n > *\n display: none\n font-size: 96px\n height: 48px\n position: absolute\n width: 48px\n\n &::after\n align-items: center\n display: flex\n height: 100%\n width: 100%\n\n &.s1, &.s2, &.s3, &.s4, &.s5, &.s6, &.s7\n > :nth-child(1)\n display: initial\n top: -24px\n left: -96px\n &.s2, &.s3, &.s4, &.s5, &.s6, &.s7\n > :nth-child(2)\n display: initial\n top: 48px\n left: -72px\n &.s3, &.s4, &.s5, &.s6, &.s7\n > :nth-child(3)\n display: initial\n top: -48px\n left: -48px\n &.s4, &.s5, &.s6, &.s7\n > :nth-child(4)\n display: initial\n top: 24px\n left: -24px\n &.s5, &.s6, &.s7\n > :nth-child(5)\n display: initial\n top: 96px\n left: 0\n &.s6, &.s7\n > :nth-child(6)\n display: initial\n top: 0px\n left: 24px\n &.s7\n > :nth-child(7)\n display: initial\n top: 72px\n left: 48px\n \n &.f1, &.f2, &.f3, &.f4, &.f5, &.f6, &.f7\n > :nth-child(1)\n display: initial\n top: 72px\n left: -96px\n &.f2, &.f3, &.f4, &.f5, &.f6, &.f7\n > :nth-child(2)\n display: initial\n top: 0px\n left: -72px\n &.f3, &.f4, &.f5, &.f6, &.f7\n > :nth-child(3)\n display: initial\n top: 96px\n left: -48px\n &.f4, &.f5, &.f6, &.f7\n > :nth-child(4)\n display: initial\n top: 24px\n left: -24px\n &.f5, &.f6, &.f7\n > :nth-child(5)\n display: initial\n top: -48px\n left: 0\n &.f6, &.f7\n > :nth-child(6)\n display: initial\n top: 48px\n left: 24px\n &.f7\n > :nth-child(7)\n display: initial\n top: -24px\n left: 48px\n\ntd\n > select\n width: 100%\n > input\n border-radius: 0\n box-shadow: none\n > aside.fx-picker\n > label\n display: none\n\n &.sprite\n text-align: center\n vertical-align: middle\n\n > img\n margin-right: 1rem\n vertical-align: middle\n \n &.input\n > input[type=number]\n display: block\n margin: auto\n width: 60px\n\nsection.settings\n overflow: auto\n padding: 1rem\n position: relative\n\n > h2\n margin: 0 0 1rem\n > button.close\n position: absolute\n top: 1rem\n right: 1rem\n\n > table\n margin: 0 -8px\n width: calc(100% + 16px)\n\nsection.demo-picker\n overflow: auto\n padding: 1rem\n position: relative\n\n > h2\n margin: 0 0 1rem\n > button.close\n position: absolute\n top: 1rem\n right: 1rem\n\n > table\n font-size: 18px\n > tbody\n > tr\n cursor: pointer\n line-height: 2rem\n\n &:hover\n background-color: rgba(103, 58, 183, 0.19)\n\nviewport\n background-attachment: local\n background-color: bg-color\n display: flex\n height: 100%\n align-items: center\n overflow-x: scroll\n overflow-y: hidden\n\n > staff\n background-color: rgba(255, 255, 255, 0.9375)\n border: 1px solid rgba(0, 0, 0, 0.5)\n box-sizing: content-box\n box-shadow: 1px 1px 0 0 rgba(0, 0, 0, 0.5)\n display: block\n flex: 0 0 auto\n padding: 96px 0\n position: relative\n margin: 0 48px\n z-index: 0\n\n // Hack for right margin inside scrollable viewport\n // Also serving as song end bar\n &::after\n border-left: 8px solid black\n content: \"\"\n position: absolute\n right: -48px\n top: 96px\n width: 48px\n height: calc(100% - 192px)\n z-index: -1\n\n > z-meter\n display: block\n position: absolute\n left: 256px\n top: 96px\n height: calc(100% - 192px)\n width: calc(100% - 256px)\n\n &::before\n content: \"\"\n border-left: 2px solid black\n position: absolute\n height: 100%\n left: -257px\n\n > div.repeat\n position: absolute\n right: 0\n height: 100%\n\n &::before, &::after\n content: \"\"\n display: block\n background-color: black\n border-radius: 100%\n position: absolute\n width: 16px\n height: 16px\n right: 16px\n top: 66px\n\n &::after\n top: 114px\n \n &.bass\n position: relative\n top: 288px\n\n > notes\n display: block\n position: absolute\n left: 256px\n top: 337px\n\n > pattern-preview\n opacity: 0.5\n position: absolute\n\n > selection\n background-color: rgba(103, 58, 183, 0.25)\n border: 2px dashed primary-color\n position: absolute\n left: -150px\n z-index: 20\n\n > button\n display: none\n position: absolute\n top: 0\n left: 0\n right: 0\n bottom: 0\n margin: auto\n width: 48px\n height: 48px\n\n &.up\n bottom: calc(100%+4px)\n top: auto\n &.down\n top: calc(100%+4px)\n bottom: auto\n &.left\n right: calc(100%+4px)\n left: auto\n &.right\n left: calc(100%+4px)\n right: auto\n\n > actions\n display: none\n position: absolute\n\n > button\n margin-left: 4px\n\n &.t > actions\n top: 0\n &.b > actions\n bottom: 0\n &.l > actions\n left: 0\n &.r > actions\n right: 0\n\n &.set \n > actions\n display: flex\n > button\n display: block\n\n > lines\n display: block\n margin-bottom: 48px\n \n &:nth-child(2)\n margin-bottom: 0\n\n > line:last-child\n height: 0\n\n > line\n border-top: 3px solid black\n display: block\n height: 48px\n\n > playhead\n border-left: 1px solid rgba(103, 58, 183, 0.5)\n border-right: 1px solid rgba(103, 58, 183, 0.5)\n position: absolute\n top: 0\n height: 674px\n width: 2px\n z-index: 10\n\n &.buffer-start\n display: none\n border-color: rgba(255, 0, 0, 0.5)\n\n &.buffer-end\n display: none\n border-color: rgba(0, 0, 255, 0.5)\n\n > img\n position: absolute\n height: 600px\n top: -94px\n left: -120px\n\n &:last-child\n left: -100px\n top: 181px\n\nform\n > label\n display: block\n\n > h3\n font-size: 1rem\n\n &.inline\n display: inline-block\n margin-right: 0.5em\n\n > input\n padding-left: 2px\n width: 100%\n\n > actions\n display: flex\n justify-content: space-between\n margin-top: 1em\n\n &.purchase\n width: 672px\n\napp\n display: flex\n flex: 1 0\n height: 100%\n flex-direction: column\n\nbutton.loop\n font-size: 32px\n line-height: 1rem\n\naside.meter-picker\n margin-left: 8px\n > button\n border-radius: 0\n\n &:first-child\n border-top-left-radius: 4px\n border-bottom-left-radius: 4px\n\n &:last-child\n border-top-right-radius: 4px\n border-bottom-right-radius: 4px\n\naside.note-control\n > section\n display: flex\n margin-left: 1rem\n \n > label\n display: flex\n align-items: center\n margin-right: 4px\n\n > button\n flex: 0 0 auto\n font-size: 20px\n line-height: 1rem\n width: 36px\n\n border-radius: 0\n\n &.triplet\n font-size: 16px\n\n &:nth-child(n + 3)\n border-left: 0\n\n &:nth-child(2)\n border-top-left-radius: 4px\n border-bottom-left-radius: 4px\n\n &.snap\n > button\n &:nth-child(5)\n border-top-right-radius: 4px\n border-bottom-right-radius: 4px\n \n > input\n margin-left: 5px\n width: 60px\n\n &.accidental\n > button:nth-child(4)\n border-top-right-radius: 4px\n border-bottom-right-radius: 4px\n\nsection.persistence\n display: flex\n flex-direction: column\n padding: 1rem\n width: 480px\n\n > button\n margin-top: 8px\n width: 100%\n\n &:first-child\n margin-top: 0\n\nabout > actions\n > button, a.button\n width: 100%\n margin-top: 8px\n\naside\n display: flex\n > label\n display: flex\n align-items: center\n margin-right: 4px\n margin-left: 1rem\n\naside.stereo-analyser\n > canvas\n border: 1px solid #673ab7\n border-radius: 4px\n\naside.fx-picker\n display: flex\n\n > button\n background-size: 100%\n border: 1px solid #673ab7\n border-radius: 4px\n height: 36px\n padding: 0\n margin-left: 2px\n width: 36px\n\naside.actions\n background-color: white\n border-top: 2px solid primary-color\n display: flex\n flex: 0 0 auto\n flex-direction: column\n padding: 4px 4px 0px 4px\n width: 100%\n\n > section:nth-child(2)\n > button, > a.button\n padding: 15px 8px\n\n > section\n display: flex\n &:last-child\n margin-top: 8px\n\n > section.buttons\n > *\n margin-bottom: 6px\n \n &:nth-child(n + 2)\n margin-left: 4px\n \n > form\n display: flex\n \n > button, > a.button\n flex: 0 0 auto\n\n > label\n align-items: center\n border: 1px solid #673ab7\n border-radius: 4px\n box-shadow: 1px 2px 0px #673ab7\n color: #673ab7\n display: flex\n flex-direction: column\n padding: 5px 8px 4px\n white-space: nowrap\n \n > input\n padding: 2px 0 0\n text-align: center\n width: 60px\n \n > h2\n display: block\n font-size: 1em\n font-weight: normal\n margin: 0 0 4px\n \n > .right\n margin-left: auto\n\nactions > label\n background-color: white\n border 1px solid primary-color\n border-radius: 4px\n box-shadow: 1px 2px 0px primary-color\n color: #673ab7\n cursor: pointer\n font: inherit\n line-height: 1em\n padding: 9px 16px\n &:nth-child(n + 2)\n margin-left: 4px\n\na.button\n align-items: center\n display: inline-flex\n justify-content: center\n text-align: center\n text-decoration: none\n\n // Hack for merch icon spacing\n > :nth-child(2)\n margin-left: 5px\n\nbutton, a.button\n background-color: white\n border 1px solid primary-color\n border-radius: 4px\n box-shadow: 1px 2px 0px primary-color\n color: #673ab7\n cursor: pointer\n font-size: inherit\n line-height: 1em\n padding: 9px 8px\n white-space: nowrap\n\n &.full\n width: 100%\n\n &:focus\n background-color: active-color\n color: rgba(0, 0, 0, 0.69)\n outline-offset: 4px\n\n &:active, &.active\n background-color: primary-color\n border 1px solid primary-color\n color: white\n box-shadow: 1px 2px 0px #241440 inset\n\n &:disabled\n background-color: #eee\n border-color: #4e4e4e\n box-shadow: 1px 2px 0px #4e4e4e\n color: #4e4e4e\n cursor: default\n\ntools, patterns\n background-color: white\n border-bottom: 2px solid primary-color\n display: flex\n width: 100%\n\n > *\n background-repeat: no-repeat\n background-position: 50% 50%\n border-right: 1px solid primary-color\n cursor: pointer\n display: block\n width: 49px\n height: 48px\n &:hover\n background-color: highlight-color\n &.active\n background-color: active-color\n\n > tool.eraser\n order: 2\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAAIdJREFUeJzNUsERwCAIw15n031wDt0Hl0s/9VoF9NnmZzRBCERfI2zusdOtDABmopRGVoRCrdviADNMiADM6L873Mql2NYiw3E2WItzVi2dSuw8JBHNvQyegcU4vmjNFesWZrHFTSlYQ/RhRDgatKZFnXPy7zMIoVaYa3fH5i3PTHira4r/gQv1W1E4p9FksQAAAABJRU5ErkJggg==\")\n\n > .selection\n order: 2\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAiUlEQVRYR+1XQQrAIAyr/3/0RLAyqoUdzNJDvFnEpkkM2MzssX21Qw1SGo0igHfNgfiZ6/sI4HqDSVs6wGlaCNXZpQ7gN80jkIxyNAtLkjIA0BOn99O0d0RlJIjvFC3JZkJ0Q3kgMqAcWIwoB8oEkXKAHsV0APIAXQI6AHkA9vf78jUbZ+gAaB7opmM7IWnG3nUAAAAASUVORK5CYII=\")\n\ntools\n > *\n pixelated()\n\npatterns\n margin-top: -1px\n\n &:empty\n display: none\n\n > pattern\n display: flex\n padding: 0 8px\n align-items: center\n width: 64px\n overflow: hidden\n\n > preview\n position: relative\n transform: scale(0.0625, 0.0625)\n top: -14px\n\n > *\n transform: scale(4)\n\npre.position\n background-color: rgba(255, 255, 255, 0.9375)\n border: 1px solid black\n box-shadow: 1px 1px 0 0 rgba(0,0,0,0.5)\n padding: 2px 6px 2px 4px\n font-family: inherit\n pointer-events: none\n padding: 4px\n position: absolute\n left: 1rem\n top: calc(45px + 1rem)\n\n &:empty\n display: none\n\npre.debug\n &:empty\n display: none\n\n@media only screen and (max-width: 768px)\n aside.actions\n border-top: none\n padding-top: 0\n > section\n &:first-child\n display: none\n &:nth-child(2)\n margin-top: 0\n\n button\n > span.description\n display: none\n\n tools, patterns\n order: 2\n border: none\n width: 49px\n height: 48px\n\n > *\n border-top: 1px solid primary-color\n display: none\n\n tool.active\n display: block\n\n pattern.active\n display: flex\n\n &.open\n background-color: primary-color\n display: grid\n grid-gap: 1px\n position: absolute\n width: 100%\n height: 100%\n grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr\n z-index: 1\n\n > *\n background-color: white\n background-size: 100%\n border: none\n display: block\n image-rendering: pixelated\n height: 100%\n width: 100%\n\n &:hover\n background-color: #FFE0B2\n"
},
"templates/about.jadelet": {
"content": "about\n h1 About\n\n p ProTip™ hold Shift to sharp, Ctrl to flat\n\n p\n | By\n a(href=\"https://danielx.net\") Daniel X. Moore\n | creator of\n a(href=\"https://whimsy.space\") Whimsy.Space\n\n p\n"
},
"templates/export.jadelet": {
"content": "form(@submit)\n h2 @title\n label\n h3 File Name\n input(value=@name)\n\n label\n h3.inline Format\n select(value=@selectedType)\n @formatOptionElements\n actions\n button(click=@cancel) Cancel\n button Export!\n"
},
"test/sample.coffee": {
"content": "Sample = require \"../sample\"\n\ndescribe \"Sample\", ->\n it \"should be able to load the default sample pack\", (done) ->\n Sample.loadPack()\n .then (samples) ->\n done()\n .catch done\n\n return\n"
},
"lib/hotkeys.coffee": {
"content": "###\nBind hotekeys. This will be replaced by the system `app` hotkey bindings at some\npoint.\n\nStatus: Deprecated\n###\n\nMousetrap = require \"./mousetrap\"\n\nmodule.exports = (I, self) ->\n self.extend\n addHotkey: (key, method) ->\n Mousetrap.bind key, (e) ->\n e.preventDefault()\n\n if typeof method is \"function\"\n method\n editor: self\n else\n self[method]()\n"
},
"lib/mousetrap.js": {
"content": "/* mousetrap v1.6.3 craig.is/killing/mice */\r\n(function(q,u,c){function v(a,b,g){a.addEventListener?a.addEventListener(b,g,!1):a.attachEvent(\"on\"+b,g)}function z(a){if(\"keypress\"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return n[a.which]?n[a.which]:r[a.which]?r[a.which]:String.fromCharCode(a.which).toLowerCase()}function F(a){var b=[];a.shiftKey&&b.push(\"shift\");a.altKey&&b.push(\"alt\");a.ctrlKey&&b.push(\"ctrl\");a.metaKey&&b.push(\"meta\");return b}function w(a){return\"shift\"==a||\"ctrl\"==a||\"alt\"==a||\r\n\"meta\"==a}function A(a,b){var g,d=[];var e=a;\"+\"===e?e=[\"+\"]:(e=e.replace(/\\+{2}/g,\"+plus\"),e=e.split(\"+\"));for(g=0;g<e.length;++g){var m=e[g];B[m]&&(m=B[m]);b&&\"keypress\"!=b&&C[m]&&(m=C[m],d.push(\"shift\"));w(m)&&d.push(m)}e=m;g=b;if(!g){if(!p){p={};for(var c in n)95<c&&112>c||n.hasOwnProperty(c)&&(p[n[c]]=c)}g=p[e]?\"keydown\":\"keypress\"}\"keypress\"==g&&d.length&&(g=\"keydown\");return{key:m,modifiers:d,action:g}}function D(a,b){return null===a||a===u?!1:a===b?!0:D(a.parentNode,b)}function d(a){function b(a){a=\r\na||{};var b=!1,l;for(l in p)a[l]?b=!0:p[l]=0;b||(x=!1)}function g(a,b,t,f,g,d){var l,E=[],h=t.type;if(!k._callbacks[a])return[];\"keyup\"==h&&w(a)&&(b=[a]);for(l=0;l<k._callbacks[a].length;++l){var c=k._callbacks[a][l];if((f||!c.seq||p[c.seq]==c.level)&&h==c.action){var e;(e=\"keypress\"==h&&!t.metaKey&&!t.ctrlKey)||(e=c.modifiers,e=b.sort().join(\",\")===e.sort().join(\",\"));e&&(e=f&&c.seq==f&&c.level==d,(!f&&c.combo==g||e)&&k._callbacks[a].splice(l,1),E.push(c))}}return E}function c(a,b,c,f){k.stopCallback(b,\r\nb.target||b.srcElement,c,f)||!1!==a(b,c)||(b.preventDefault?b.preventDefault():b.returnValue=!1,b.stopPropagation?b.stopPropagation():b.cancelBubble=!0)}function e(a){\"number\"!==typeof a.which&&(a.which=a.keyCode);var b=z(a);b&&(\"keyup\"==a.type&&y===b?y=!1:k.handleKey(b,F(a),a))}function m(a,g,t,f){function h(c){return function(){x=c;++p[a];clearTimeout(q);q=setTimeout(b,1E3)}}function l(g){c(t,g,a);\"keyup\"!==f&&(y=z(g));setTimeout(b,10)}for(var d=p[a]=0;d<g.length;++d){var e=d+1===g.length?l:h(f||\r\nA(g[d+1]).action);n(g[d],e,f,a,d)}}function n(a,b,c,f,d){k._directMap[a+\":\"+c]=b;a=a.replace(/\\s+/g,\" \");var e=a.split(\" \");1<e.length?m(a,e,b,c):(c=A(a,c),k._callbacks[c.key]=k._callbacks[c.key]||[],g(c.key,c.modifiers,{type:c.action},f,a,d),k._callbacks[c.key][f?\"unshift\":\"push\"]({callback:b,modifiers:c.modifiers,action:c.action,seq:f,level:d,combo:a}))}var k=this;a=a||u;if(!(k instanceof d))return new d(a);k.target=a;k._callbacks={};k._directMap={};var p={},q,y=!1,r=!1,x=!1;k._handleKey=function(a,\r\nd,e){var f=g(a,d,e),h;d={};var k=0,l=!1;for(h=0;h<f.length;++h)f[h].seq&&(k=Math.max(k,f[h].level));for(h=0;h<f.length;++h)f[h].seq?f[h].level==k&&(l=!0,d[f[h].seq]=1,c(f[h].callback,e,f[h].combo,f[h].seq)):l||c(f[h].callback,e,f[h].combo);f=\"keypress\"==e.type&&r;e.type!=x||w(a)||f||b(d);r=l&&\"keydown\"==e.type};k._bindMultiple=function(a,b,c){for(var d=0;d<a.length;++d)n(a[d],b,c)};v(a,\"keypress\",e);v(a,\"keydown\",e);v(a,\"keyup\",e)}if(q){var n={8:\"backspace\",9:\"tab\",13:\"enter\",16:\"shift\",17:\"ctrl\",\r\n18:\"alt\",20:\"capslock\",27:\"esc\",32:\"space\",33:\"pageup\",34:\"pagedown\",35:\"end\",36:\"home\",37:\"left\",38:\"up\",39:\"right\",40:\"down\",45:\"ins\",46:\"del\",91:\"meta\",93:\"meta\",224:\"meta\"},r={106:\"*\",107:\"+\",109:\"-\",110:\".\",111:\"/\",186:\";\",187:\"=\",188:\",\",189:\"-\",190:\".\",191:\"/\",192:\"`\",219:\"[\",220:\"\\\\\",221:\"]\",222:\"'\"},C={\"~\":\"`\",\"!\":\"1\",\"@\":\"2\",\"#\":\"3\",$:\"4\",\"%\":\"5\",\"^\":\"6\",\"&\":\"7\",\"*\":\"8\",\"(\":\"9\",\")\":\"0\",_:\"-\",\"+\":\"=\",\":\":\";\",'\"':\"'\",\"<\":\",\",\">\":\".\",\"?\":\"/\",\"|\":\"\\\\\"},B={option:\"alt\",command:\"meta\",\"return\":\"enter\",\r\nescape:\"esc\",plus:\"+\",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?\"meta\":\"ctrl\"},p;for(c=1;20>c;++c)n[111+c]=\"f\"+c;for(c=0;9>=c;++c)n[c+96]=c.toString();d.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};d.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};d.prototype.trigger=function(a,b){if(this._directMap[a+\":\"+b])this._directMap[a+\":\"+b]({},a);return this};d.prototype.reset=function(){this._callbacks={};\r\nthis._directMap={};return this};d.prototype.stopCallback=function(a,b){if(-1<(\" \"+b.className+\" \").indexOf(\" mousetrap \")||D(b,this.target))return!1;if(\"composedPath\"in a&&\"function\"===typeof a.composedPath){var c=a.composedPath()[0];c!==a.target&&(b=c)}return\"INPUT\"==b.tagName||\"SELECT\"==b.tagName||\"TEXTAREA\"==b.tagName||b.isContentEditable};d.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};d.addKeycodes=function(a){for(var b in a)a.hasOwnProperty(b)&&(n[b]=a[b]);p=null};\r\nd.init=function(){var a=d(u),b;for(b in a)\"_\"!==b.charAt(0)&&(d[b]=function(b){return function(){return a[b].apply(a,arguments)}}(b))};d.init();\"undefined\"!==typeof module&&module.exports&&(module.exports=d);\"function\"===typeof define&&define.amd&&define(function(){return d})}})(\"undefined\"!==typeof window?window:null,\"undefined\"!==typeof window?document:null);\r\n"
},
"lib/model.coffee": {
"content": "###\nModel\n=====\n\nThe `Model` module provides helper methods to compose nested data models.\n\nWill be replaced by the model from `system` once that is more fully developed.\n\nStatus: Deprecated\n###\n\n# Models uses [Observable](/observable/docs) to keep the internal data in sync.\n\n{Observable} = system.ui\n\nmodule.exports = Model = (I={}, self={}) ->\n Object.assign self,\n\n ###\n `I` holds the instance state. It is generally considered private, but access\n is available for debugging and other purposes.\n ###\n I: I\n\n ###\n Generates a public jQuery style getter / setter method for each `String` argument.\n \n > #! example\n > myObject = Model\n > r: 255\n > g: 0\n > b: 100\n >\n > myObject.attrAccessor \"r\", \"g\", \"b\"\n >\n > myObject.r(254)\n ###\n attrAccessor: (attrNames...) ->\n attrNames.forEach (attrName) ->\n self[attrName] = (newValue) ->\n if arguments.length > 0\n I[attrName] = newValue\n\n return self\n else\n I[attrName]\n\n return self\n \n ###\n Generates a public jQuery style getter method for each String argument.\n \n > #! example\n > myObject = Model\n > r: 255\n > g: 0\n > b: 100\n >\n > myObject.attrReader \"r\", \"g\", \"b\"\n >\n > [myObject.r(), myObject.g(), myObject.b()]\n \n ###\n attrReader: (attrNames...) ->\n attrNames.forEach (attrName) ->\n self[attrName] = ->\n I[attrName]\n\n return self\n\n ###\n Extends this object with methods from the passed in object. A shortcut for Object.extend(self, methods)\n \n > I =\n > x: 30\n > y: 40\n > maxSpeed: 5\n >\n > # we are using extend to give player\n > # additional methods that Model doesn't have\n > player = Model(I).extend\n > increaseSpeed: ->\n > I.maxSpeed += 1\n >\n > player.increaseSpeed()\n \n ###\n extend: (objects...) ->\n Object.assign self, objects...\n\n ###\n Includes a module in this object. A module is a constructor that takes two parameters, `I` and `self`\n \n > myObject = Model()\n > myObject.include(Bindable)\n \n > # now you can bind handlers to functions\n > myObject.bind \"someEvent\", ->\n > alert(\"wow. that was easy.\")\n ###\n include: (modules...) ->\n for Module in modules\n Module(I, self)\n\n return self\n\n ###\n Bind a data model getter/setter to an attribute. The data model is bound directly to\n the attribute and must be directly convertible to and from JSON.\n ###\n attrData: (name, DataModel) ->\n I[name] = DataModel(I[name])\n\n Object.defineProperty self, name,\n get: ->\n I[name]\n set: (value) ->\n I[name] = DataModel(value)\n\n ###\n Observe any number of attributes as observables. For each attribute name passed in we expose a public getter/setter method and listen to changes when the value is set.\n ###\n attrObservable: (names...) ->\n names.forEach (name) ->\n self[name] = Observable(I[name])\n\n self[name].observe (newValue) ->\n I[name] = newValue\n\n return self\n\n # Experimental, observe with a writer function\n observable: (name, writer) ->\n o = Observable writer I[name]\n\n o.observe (newValue) ->\n I[name] = writer newValue\n\n self[name] = (v) ->\n if arguments.length > 0\n o writer v\n else\n o()\n\n ###\n Observe an attribute as a model. Treats the attribute given as an Observable\n model instance exposing a getter/setter method of the same name. The Model\n constructor must be passed explicitly.\n ###\n attrModel: (name, Model) ->\n model = Model(I[name])\n\n self[name] = Observable(model)\n\n self[name].observe (newValue) ->\n I[name] = newValue.I\n\n return self\n\n ###\n Observe an attribute as an array of sub-models. This is the same as `attrModel`\n except the attribute is expected to be an array of models rather than a single one.\n ###\n attrModels: (name, Model) ->\n models = (I[name] or []).map (x) ->\n Model(x)\n\n self[name] = Observable(models)\n\n self[name].observe (newValue) ->\n I[name] = newValue.map (instance) ->\n instance.I\n\n return self\n\n ###\n Delegate methods to another target. Makes it easier to compose rather than extend.\n ###\n delegate: (names..., {to}) ->\n names.forEach (name) ->\n Object.defineProperty self, name,\n get: ->\n receiver = getValue self, to\n receiver[name]\n set: (value) ->\n receiver = getValue self, to\n setValue receiver, name, value\n\n ###\n The JSON representation is kept up to date via the observable properites and resides in `I`.\n ###\n toJSON: ->\n I\n\n return self\n\nisFn = (x) ->\n typeof x is 'function'\n\ngetValue = (receiver, property) ->\n if isFn receiver[property]\n receiver[property]()\n else\n receiver[property]\n\nsetValue = (receiver, property, value) ->\n target = receiver[property]\n\n if isFn target\n target.call(receiver, value)\n else\n receiver[property] = value\n\ndefaults = (target, objects...) ->\n for object in objects\n for name of object\n unless target.hasOwnProperty(name)\n target[name] = object[name]\n\n return target\n\nextend = Object.assign\n\nObject.assign Model, {Observable, defaults, extend}\n"
},
"data/templates/export.coffee": {
"content": ""
},
"data/templates/feedback-tab.coffee": {
"content": ""
},
"templates/actions.jadelet": {
"content": "aside.actions\n section\n button.loop(click=@toggleLoop class=@loopButtonClass) 𝄇\n @meterPickerElement\n @noteControlElement\n @fxPickerElement\n @stereoAnalyserElement\n section.buttons\n button.play(click=@play title=\"Play\" class=@playButtonClass)\n span.icon ▶️\n |\n span.description Play\n button(click=@rewind title=\"Rewind\")\n span.icon ⏪\n |\n span.description Go to Start\n label\n h2 Tempo\n input(value=@tempo keydown=@inputHotkeys type=\"number\" min=1 step=1)\n label\n h2 Length\n input(value=@length keydown=@inputHotkeys type=\"number\" min=4 step=4)\n button(class=@hiddenUnlessPurchased click=@showArranger)\n span.icon 📑\n |\n span.description Sections\n button(click=@undo disabled=@undoDisabled)\n span.icon ↩️\n |\n span.description Undo\n button(click=@redo disabled=@redoDisabled)\n span.icon ↪️\n |\n span.description Redo\n button(click=@clear disabled=@clearDisabled)\n span.icon 💣\n |\n span.description Clear\n button(click=@showSpriteConfig)\n span.icon ⚙️\n |\n span.description Settings\n button(click=@showPersistenceModal)\n span.icon 💾\n |\n span.description Save/Load\n"
},
"templates/tools.jadelet": {
"content": "tools(class=@toolsClass click=@toggleToolsOpen)\n @sampleTools\n tool.eraser(class=@eraserActive click=@eraser)\n tool.selection(class=@selectionActive click=@selection)\n"
},
"templates/app.jadelet": {
"content": "app\n @toolsElement\n @patternsElement\n @staffElement\n @actionsElement\n pre.position @notePosition\n"
},
"data/templates/tools.coffee": {
"content": "{Observable} = system.ui\n\nSample = require \"../../sample\"\n\nsamples = Observable []\n\nSample.loadPack()\n.then samples\n\nmodule.exports =\n samples: samples\n activeToolIndex: Observable()\n activeInstrument: Observable()\n playNote: ->\n"
},
"data/templates/actions.coffee": {
"content": "module.exports =\n tempo: 120\n beats: 8\n redoDisabled: true\n hiddenIfPurchased: -> #\"hidden\"\n noteControlElement: require(\"/views/note-control\")().element\n stereoAnalyserElement: require(\"/views/stereo-analyser\")(new AudioContext).element\n hiddenUnlessPurchased: -> \"hidden\"\n"
},
"data/templates/app.coffee": {
"content": ""
},
"templates/staff.jadelet": {
"content": "viewport(@contextmenu @dragstart @scroll @mousedown @mousemove @mouseup style=@fxStyle)\n staff\n lines\n line\n line\n line\n line\n line\n\n lines\n line\n line\n line\n line\n line\n\n z-meter\n div.repeat(class=@repeatClass)\n div.repeat.bass(class=@repeatClass)\n notes\n selection(class=@selectionClass)\n button.up(click=@selectionMoveUp) 🡅\n button.down(click=@selectionMoveDown) 🡇\n button.left(click=@selectionMoveLeft) 🡄\n button.right(click=@selectionMoveRight) 🡆\n actions\n button(click=@selectionCopy) Copy\n button(click=@selectionCut) Cut\n button(click=@selectionDelete) Delete\n button(click=@selectionReset) Cancel\n pattern-preview\n playhead\n playhead.buffer-start\n playhead.buffer-end\n\n img(src=\"https://danielx.net/composer/treble-clef.svg\")\n img(src=\"https://danielx.net/composer/bass-clef.svg\")\n"
},
"data/templates/staff.coffee": {
"content": "module.exports =\n contextmenu: (e) -> e.preventDefault()\n selectionClass: \"set\""
},
"song-v2.coffee": {
"content": "###\nSong V2\n=======\n\nA song has a list of sections.\n\nEach section has:\n\nname: string\nlength: beats\nnotes: array<Note>\nsettings: [{\n pan: float [-1, 1]\n pitchShift: float (semitones)\n volume: float\n}, ...]\n\n###\n\n{pitchToStaffNote, staffNoteToPitch, noteCompare, quantize} = require \"./lib/util\"\n\nSetting = (I={}, self=Model(I)) ->\n self.attrObservable \"url\", \"name\"\n \n [\"pan\", \"pitchShift\", \"volume\"].forEach (name) ->\n self[name] = Observable(I[name])\n\n self[name].observe (newValue) ->\n I[name] = Number newValue\n\n self.toJSON = ->\n pan: I.pan\n pitchShift: I.pitchShift\n volume: I.volume\n\n return self\n\nPattern = require \"./lib/pattern\"\nSection = require \"./lib/section\"\n\nOldSong = require \"/lib/legacy/song\"\n\noldInstrumentMapping =\n 0: 0\n 1: 1\n 2: 2\n 3: 4\n 4: 5\n 5: 8\n 6: 9\n 7: 11\n 8: 16\n 9: 17\n\nmodule.exports = (I={}, self=Model(I)) ->\n defaults I,\n title: \"Untitled\"\n description: \"\"\n sections: [{\n name: \"Section 1\"\n length: 64\n tempo: 120\n notes: []\n }]\n settings: []\n loop: true\n version: 2\n\n self.attrObservable \"loop\"\n self.attrModels \"sections\", Section\n self.attrModels \"settings\", Setting\n\n # Note -> section lookup\n sectionFor = null\n\n self.extend\n # Clear entire song, keeping length, tempo, FX from first section, removing\n # all other sections\n clear: ->\n section = self.sections()[0]\n section.notes []\n\n self.sections [ section ]\n\n loadSettings: (samples) ->\n self.settings samples.map (sample) ->\n Setting sample\n\n # Patterns and ranges get distributed to the underlying sections\n addPattern: ([time, note], pattern) ->\n pattern.notes().forEach ([t, n, rest...]) ->\n self.addNote [time + t, note + n, rest...]\n\n # Copy notes within a range to a Pattern\n # normalize the patten so that the first note is 0,0\n copyRange: (range) ->\n t = range[0][0]\n dt = range[0][1] - t\n\n notes = self.notesWithinRange(range)\n\n first = notes[0]\n if first\n # normalize to first note\n [t0, n0] = first\n notes = notes.map ([t, n, rest...]) ->\n [t - t0, n - n0, rest...]\n\n Pattern\n beats: dt\n notes: notes\n\n deleteRange: (range) ->\n time = 0\n self.sections.forEach (section) ->\n l = section.length()\n section.deleteRange([[range[0][0] - time, range[0][1] - time], range[1]])\n time += l\n\n ###\n Pass all the notes that are upcoming to a handler fn\n The handler fn receives:\n\n note - a stable reference to a note that can be used as a key to an element\n section - a reference to the section object the note appears in\n s - seconds after beat t, accounts for different section bpm\n\n i.e. notes that occur within the interval [t, t+dt)\n\n t and dt are in beats\n\n doesn't return notes outside of [0, length)\n\n current is expected to be <= t\n\n if current is < 0 then it is considered to wrap from the end\n\n current is an optional reference time to receive the accumulated seconds\n relative to. This is used in the audio playback.\n ###\n upcomingNotes: (t, dt, fn, current=t) ->\n accumulatedBeats = 0\n accumulatedTime = 0\n songLength = self.length()\n songDuration = self.duration()\n\n if songLength <= 0 or isNaN(songLength)\n return\n\n if t >= songLength\n return\n\n # Get current up to zero and accumulate time from wrapping\n while current < 0\n accumulatedTime += songDuration\n current += songLength\n\n # Remove any overshoot and match the zero to the begining of the song\n if current > 0\n self.sections().forEach (section) ->\n sectionLength = section.length()\n sectionDuration = section.duration()\n if sectionLength < current\n current -= sectionLength\n accumulatedTime -= sectionDuration\n else if current > 0\n accumulatedTime -= (current / sectionLength) * sectionDuration\n current = 0\n\n # Walk through the sections accumulating beats and time, passing notes\n # within the target time-slice to the handler with their section and\n # seconds since reference time.\n self.sections().forEach (section) ->\n sectionLength = section.length()\n sectionDuration = section.duration()\n\n section.upcomingNotes t - accumulatedBeats, dt, (note) ->\n beat = note[0]\n s = (beat / sectionLength) * sectionDuration + accumulatedTime\n\n fn(note, section, s)\n\n accumulatedBeats += sectionLength\n accumulatedTime += sectionDuration\n\n # Return an array containing the notes within a time slice\n notesWithin: (t, dt) ->\n notes = []\n self.upcomingNotes t, dt, (note) ->\n notes.push note\n return notes\n\n notesWithinRange: (range) ->\n t = range[0][0]\n dt = range[0][1] - t\n [n0, n1] = range[1]\n\n notes = self.notesWithin(t, dt + 0.0001).filter( ([_, n]) ->\n n0 <= n <= n1\n ).sort noteCompare\n\n # returns [section, beatsLeft]\n # t and time in beats\n sectionAt: (t) ->\n time = 0\n i = 0\n sections = self.sections()\n\n while section = sections[i]\n time += section.length()\n\n if t < time\n return [section, time - t]\n\n i++\n\n return []\n\n addNote: (note) ->\n time = 0\n t = note[0]\n self.sections.forEach (section) ->\n l = section.length()\n if 0 <= t - time < l\n note[0] -= time\n section.addNote(note)\n # Keep the section lookup cache up to date\n sectionFor.set(note, section)\n time += l\n\n removeNote: (note, nearby) ->\n time = 0\n t = note[0]\n removed = false\n self.sections.forEach (section) ->\n l = section.length()\n if 0 <= t - time < l\n note[0] -= time\n removed = section.removeNote(note, nearby)\n time += l\n\n return removed\n\n # return beat at which a section begins\n sectionBeginsAt: (s) ->\n t = 0\n ret = undefined\n\n self.sections.forEach (section) ->\n if section is s\n ret = t\n t += s.length()\n\n return ret\n\n # seconds\n duration: ->\n self.sections.reduce (total, section) ->\n total + section.duration()\n , 0\n\n # seconds\n # Sum of all section durations, but the last section crops all empty measures\n exportDuration: ->\n last = self.sections().length - 1\n\n self.sections.reduce (total, section, i) ->\n if i is last\n total + section.nonEmptyDuration()\n else\n total + section.duration()\n , 0\n\n # Length is the sum of the length of all the sections\n length: ->\n self.sections().reduce (count, section) ->\n count + section.length()\n , 0\n\n # Reset cached note -> section lookup\n reset: ->\n sectionFor = new Map\n\n self.upcomingNotes 0, self.length(), (note, section) ->\n sectionFor.set(note, section)\n\n # When moving a selection of notes we need to 'resection' any that fall \n # across section boundaries\n resection: (notes) ->\n notes.forEach (note) ->\n section = sectionFor.get(note)\n\n [t] = note\n if (t < 0) or (t >= section.length())\n # Remove from old section\n # add at proper time, inserting into new section\n sectionStart = self.sectionBeginsAt(section)\n\n # Note can float negative in first section\n if sectionStart is 0 and t < 0\n return\n\n if section.removeNoteByReference(note)\n note[0] += sectionStart\n self.addNote(note)\n\n toJSON: ->\n Object.assign {}, I, {\n settings: self.settings.map (s) -> s.toJSON()\n }\n\n fromJSON: (data) ->\n # Load old style if necessary\n if data.patterns or data.notes\n song = OldSong().fromJSON(data)\n notes = song.upcomingNotes(0, song.size())\n newNotes = notes.map ([beat, note, instrument]) ->\n [beat, pitchToStaffNote(note)..., oldInstrumentMapping[instrument]]\n\n section = self.sections()[0]\n\n section.notes newNotes\n lastTime = newNotes.reduce (max, [t]) ->\n Math.max(t, max)\n , 0\n section.length quantize(lastTime + 1.99999, 4)\n section.tempo song.tempo()\n\n else\n self.sections data.sections.map (s) -> Section s\n\n if data.settings\n debugger\n data.settings.forEach (s, i) ->\n setting = self.settings()[i]\n\n Object.keys(s).forEach (key) ->\n setting[key] s[key]\n\n # Convert old F-pitched notes\n if data.version is undefined\n self.sections.forEach (section) ->\n notes = section.I.notes\n notes.forEach (n, i) ->\n [beat, staffNote, accidental, instrument] = notes[i]\n pitch = staffNoteToPitch(staffNote, accidental) + 5\n [staffNote, accidental] = pitchToStaffNote(pitch)\n notes[i][1] = staffNote\n notes[i][2] = accidental\n\n self.reset()\n\n return self\n\n self.reset()\n\n return self\n"
},
"templates/note.jadelet": {
"content": "note(@style class=@note class=@accidental class=@instrument)\n"
},
"data/templates/note.coffee": {
"content": "module.exports =\n note: \"C4\"\n accidental: \"♭\"\n instrument: \"dog\"\n"
},
"lib/util.coffee": {
"content": "###\nUtil\n\nHolds a bunch of methods for note math, conversions, some dom stuff.\n\nStatus: Active\n###\n\n# https://en.wikipedia.org/wiki/Circle_of_fifths\n# Positive is number of sharps, negative is number of flats\n# Maps staff position to scale pitch\n# zero is C major / A minor\nscale =\n \"-7\": # Cb\n 0: -1\n 1: 1\n 2: 3\n 3: 4\n 4: 6\n 5: 8\n 6: 10\n \"-6\": # Gb / eb\n 0: -1\n 1: 1\n 2: 3\n 3: 5\n 4: 6\n 5: 8\n 6: 10\n \"-5\": # Db / bb\n 0: 0\n 1: 1\n 2: 3\n 3: 5\n 4: 6\n 5: 8\n 6: 10\n \"-4\": # Ab / f\n 0: 0\n 1: 1\n 2: 3\n 3: 5\n 4: 7\n 5: 8\n 6: 10\n \"-3\": # Eb / c\n 0: 0\n 1: 2\n 2: 3\n 3: 5\n 4: 7\n 5: 8\n 6: 10\n \"-2\": # Bb / g\n 0: 0\n 1: 2\n 2: 3\n 3: 5\n 4: 7\n 5: 9\n 6: 10\n \"-1\": # F / d\n 0: 0\n 1: 2\n 2: 4\n 3: 5\n 4: 7\n 5: 9\n 6: 10\n 0: # C / a\n 0: 0\n 1: 2\n 2: 4\n 3: 5\n 4: 7\n 5: 9\n 6: 11\n 1: # G / e\n 0: 0\n 1: 2\n 2: 4\n 3: 6\n 4: 7\n 5: 9\n 6: 11\n 2: # D / b\n 0: 1\n 1: 2\n 2: 4\n 3: 6\n 4: 7\n 5: 9\n 6: 11\n 3: # A / f#\n 0: 1\n 1: 2\n 2: 4\n 3: 6\n 4: 8\n 5: 9\n 6: 11\n 4: # E / c#\n 0: 1\n 1: 3\n 2: 4\n 3: 6\n 4: 8\n 5: 9\n 6: 11\n 5: # B / g#\n 0: 1\n 1: 3\n 2: 4\n 3: 6\n 4: 8\n 5: 10\n 6: 11\n 6: # F# / d#\n 0: 1\n 1: 3\n 2: 5\n 3: 6\n 4: 8\n 5: 10\n 6: 11\n 7: # C#\n 0: 1\n 1: 3\n 2: 5\n 3: 6\n 4: 8\n 5: 10\n 6: 12\n\ninverseScale = Object.keys(scale[0]).reduce (m, key) ->\n value = scale[0][key]\n m[value] = key|0\n return m\n, {}\n\nnotes = \"C C# D D# E F F# G G# A A# B\".split(\" \")\n\nwrap = (array, index) ->\n array[mod index, array.length]\n\nmod = (n, k) ->\n (n % k + k) % k\n\n###\nQuantize a number, rounding it to the nearest nth unit.\n\n assert.equal quantize(61, 4), 60\n assert.equal quantize(63, 4), 64\n\n assert.equal quantize(0.77, 0.25), 0.75\n assert.equal quantize(0.55, 0.25), 0.5\n\nIf the unit is zero then return the number as is.\n\n assert.equal quantize(0.33333, 0), 0.33333\n\n###\nquantize = (x, n) ->\n if n is 0\n return x\n\n ((x / n + 1/2)|0)*n\n\n# Compare two notes, earlier times first, then lower notes\nnoteCompare = ([ta, na], [tb, nb]) ->\n if ta < tb\n -1\n else if ta > tb\n 1\n else\n if na < nb\n -1\n else if na > nb\n 1\n else\n 0\n\nclamp = (x, min, max) ->\n Math.min(max, Math.max(min, x))\n\ncomposedPath = (el) ->\n path = []\n\n while el\n path.push el\n\n if el.tagName is 'HTML'\n path.push document\n path.push window\n break\n\n el = el.parentElement\n\n return path\n\nstaffNoteToPitch = (n, a=0, key=0) ->\n r = mod n, 7\n o = Math.floor(n/7)\n\n o * 12 + scale[key][r] + a\n\nmodule.exports =\n accidentalToSymbol: (accidental) ->\n if accidental is 1\n \"♯\"\n else if accidental is -1\n \"♭\"\n else\n \"\"\n\n composedPath: composedPath\n\n clamp: clamp\n\n mod: mod\n\n noteCompare: noteCompare\n\n quantize: quantize\n\n emptyElement: (el) ->\n while el.lastChild\n el.lastChild.remove()\n \n inRange: (range, [t, n]) ->\n [[t0, t1],[n0,n1]] = range\n\n t0 <= t <=t1 and n0 <= n <= n1\n \n ###\n noteName\n \n Translate a number to a music note name.\n \n -1 is B3\n 0 is C4\n 1 is C#4\n ...\n ###\n noteName: (noteNumber) ->\n noteNumber |= 0\n\n note = wrap notes, noteNumber\n\n octave = 4 + (noteNumber / 12)|0\n\n \"#{note}#{octave}\"\n\n patternSampleNotes: (pattern, offset=0) ->\n pattern.notes().reduce (notes, [_, staffNote, accidental, instrument]) ->\n if notes.length < 4\n pitch = staffNoteToPitch staffNote+offset, accidental\n instrumentPitchExists = notes.some ([i, p]) ->\n i is instrument and p is pitch\n\n unless instrumentPitchExists\n notes.push [instrument, pitch]\n\n return notes\n , []\n\n # Returns [scale degree, accidental]\n pitchToStaffNote: (n) ->\n r = mod n, 12\n o = Math.floor(n/12)\n\n staffNote = inverseScale[r]\n if staffNote?\n a = 0\n else\n staffNote = inverseScale[r - 1]\n a = 1\n\n [o * 7 + staffNote, a]\n\n # a is accidental 0, +1, or -1\n staffNoteToPitch: staffNoteToPitch\n\n wrap: wrap\n"
},
"lib/test/util.coffee": {
"content": "{emptyElement, quantize, staffNoteToPitch, pitchToStaffNote} = require \"../util\"\n\ndescribe \"Util\", ->\n it \"should quantize\", ->\n assert.equal quantize(61, 4), 60\n assert.equal quantize(63, 4), 64\n\n assert.equal quantize(0.77, 0.25), 0.75\n assert.equal quantize(0.55, 0.25), 0.5\n\n assert.equal quantize(0.33333, 0), 0.33333\n\n it \"should empty elements\", ->\n d = document.createElement \"div\"\n\n d.appendChild document.createElement \"a\"\n d.appendChild document.createElement \"a\"\n d.appendChild document.createElement \"a\"\n d.appendChild document.createElement \"a\"\n\n assert.equal d.children.length, 4\n emptyElement d\n assert.equal d.children.length, 0\n\n it \"should give the pitch for a key signature\", ->\n p = staffNoteToPitch 0, 0, 0\n assert.equal p, 0\n\n p = staffNoteToPitch 3, 0, 0\n assert.equal p, 5\n\n p = staffNoteToPitch 3, 0, 1\n assert.equal p, 6\n\n it \"should convert pitch to staff notes\", ->\n assert.equal pitchToStaffNote(0)[0], 0\n"
},
"lib/section.coffee": {
"content": "###\nSection\n=======\n\nA `Section` is a list of [beat, staffNote, accidental, instrument] tuples.\n\nIt includes a length in beats and a tempo in bpm.\n\n###\n\n{inRange, quantize} = require \"./util\"\nrequire \"./extensions\"\n\n{min, max} = Math\n\nmodule.exports = (I={}, self=Model(I)) ->\n defaults I,\n keySignature: 0 # [-7, 7]\n length: 64 # beats\n name: \"Untitled Section\"\n notes: []\n presetName: \"purple\"\n tempo: 90 # bpm\n timeSignature: \"4/4\"\n\n I.keySignature = I.keySignature|0 # Force Int (previous versions had an unused \"C\")\n I.length = I.length|0 # Force Int\n\n self.attrObservable \"keySignature\", \"name\", \"tempo\", \"notes\", \"presetName\", \"timeSignature\"\n\n self.observable \"length\", (value) ->\n v = parseFloat(value) or 0\n min max(1, v), 9999\n\n self.extend\n addNote: (note) ->\n self.notes.push(note)\n\n removeNoteByReference: (note) ->\n index = self.notes.indexOf(note)\n\n if index >= 0\n return self.notes.splice(index, 1)\n\n # Remove a note, checking first for the exact match\n # then for a near note. We should be able to erase triplets\n # without having to change the quantization.\n removeNote: ([time, note], nearby) ->\n [..., matched] = self.notes.filter ([t, n]) ->\n note is n and time is t\n\n if matched\n return self.notes.splice(self.notes.indexOf(matched), 1)\n\n if nearby\n {at, within} = nearby\n\n matches = self.notes.filter ([t, n]) ->\n note is n and (-within < t - at < within)\n\n if matches.length\n matches.sort (a, b) ->\n Math.abs(a[0] - at) - Math.abs(b[0] - at)\n\n matched = matches[0]\n return self.notes.splice(self.notes.indexOf(matched), 1)\n\n deleteRange: (range) ->\n self.notes self.notes.filter (beatNote) ->\n !inRange(range, beatNote)\n\n duration: ->\n self.length() * 60 / self.tempo()\n\n nonEmptyDuration: ->\n lastBeatTime = self.notes.reduce (lastBeat, note) ->\n Math.max lastBeat, note[0]\n , 0\n\n quantizedLastBeat = quantize lastBeatTime, 4\n\n if quantizedLastBeat <= lastBeatTime\n t = quantizedLastBeat + 4\n else\n t = quantizedLastBeat\n\n return t * 60 / self.tempo()\n\n numerator: ->\n parseInt(self.timeSignature()) or 4\n\n # rescale tempo\n retempo: (s) ->\n self.tempo self.tempo() * s\n self.notes.forEach (n) ->\n n[0] *= s\n self.length self.length() * s\n\n return self\n\n # `t` and `dt` are in beats.\n # The handler f is called once with each note in the time slice\n upcomingNotes: (t, dt, f) ->\n return if t + dt < 0\n return if t >= I.length\n\n self.notes.forEach (note) ->\n beat = note[0]\n\n if t <= beat < t + dt\n f note\n"
},
"lib/test/section.coffee": {
"content": "Section = require \"../section\"\n\ndescribe \"Section\", ->\n it \"should return notes within a timeframe\"\n\n it \"should know its duration\", ->\n section = Section\n length: 120\n tempo: 60\n\n assert.equal section.duration(), 120\n\n it \"should know the numerator of its time signature\", ->\n section = Section\n length: 120\n tempo: 60\n timeSignature: \"17/9\"\n\n assert.equal section.numerator(), 17\n"
},
"lib/extensions.coffee": {
"content": "{Observable} = system.ui\n\n# Polyfill for Object.assign\nextend = (target, sources...) ->\n sources.forEach (source) ->\n Object.keys(source).forEach (key) ->\n if Object.prototype.hasOwnProperty.call(source, key)\n target[key] = source[key]\n\nObject.assign ?= extend\n\nObject.assign global,\n Model: require \"./model\"\n Observable: Observable\n defaults: (target, values) ->\n Object.keys(values).forEach (key) ->\n unless Object.prototype.hasOwnProperty.call(target, key)\n target[key] = values[key]\n return target\n\n# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat#Polyfill\nif !Array.prototype.flat\n flat = (array, depth, flattened) ->\n array.forEach (el) ->\n if Array.isArray(el) and depth > 0\n flat(el, depth - 1, flattend)\n else\n flattend.push(el)\n\n Array.prototype.flat = (depth) ->\n flattend = []\n\n flat(this, Math.floor(depth) || 1, flattened)\n\n return flattend\n"
},
"lib/test/extensions.coffee": {
"content": ""
},
"lib/undo.coffee": {
"content": "###\nProvides a basic undo stack storing opaque states. The consumer is\nexpected to provide a `restoreState` method which is invoked when undoing or\nredoing.\n\nThe consumer should call `pushState` to push states onto the undo stack.\n###\n\n{Observable} = system.ui\n\nmodule.exports = (self) ->\n undoStack = []\n undoIndex = Observable -1\n\n maxSize = 20\n\n undoDisabled = ->\n undoIndex() <= 0\n\n redoDisabled = ->\n undoIndex() >= undoStack.length - 1\n\n popState = ->\n unless undoDisabled()\n undoStack[undoIndex.decrement(1)]\n\n nextState = ->\n unless redoDisabled()\n undoStack[undoIndex.increment(1)]\n\n pushState = (state) ->\n # Discard\n undoStack = undoStack.slice(0, undoIndex()+1).slice(-maxSize)\n\n # Push\n undoIndex undoStack.push(state) - 1\n\n Object.assign self,\n undo: ->\n if state = popState()\n self.restoreState(state)\n\n redo: ->\n if state = nextState()\n self.restoreState(state)\n\n undoDisabled: undoDisabled\n redoDisabled: redoDisabled\n\n pushState: pushState\n undoStack: ->\n undoStack\n"
},
"lib/test/undo.coffee": {
"content": "Undo = require \"../undo\"\n\ndescribe \"Undo\", ->\n it \"should track state\", ->\n u = Undo\n restoreState: (s) ->\n @_state = s\n\n sA = {}\n sB = {}\n sC = {}\n\n assert.equal u.undoDisabled(), true\n u.pushState(sA)\n assert.equal u.undoDisabled(), true\n u.pushState(sB)\n assert.equal u.undoDisabled(), false\n u.pushState(sC)\n assert.equal u.undoDisabled(), false\n\n u.undo()\n assert.equal u._state, sB\n\n u.undo()\n assert.equal u._state, sA\n\n # Empty stack, can't undo further\n u.undo()\n assert.equal u._state, sA\n\n u.redo()\n assert.equal u._state, sB\n\n u.redo()\n assert.equal u._state, sC\n \n u.undo()\n u.undo()\n\n sD = {}\n sE = {}\n u.pushState(sD)\n\n assert.equal u._state, sA\n assert.equal u.redoDisabled(), true\n u.pushState(sE)\n assert.equal u.redoDisabled(), true\n\n u.undo()\n assert.equal u._state, sD\n\n u.redo()\n assert.equal u._state, sE\n\n it \"should have a max size\", ->\n u = Undo\n restoreState: (s) ->\n @_state = s\n\n states = [0...25].map (i) ->\n n: i\n\n states.forEach (s) ->\n u.pushState(s)\n\n assert.equal u.redoDisabled(), true\n assert.equal u.undoDisabled(), false\n\n [0...20].forEach ->\n u.undo()\n\n assert.equal u.undoDisabled(), true\n assert.equal u._state, states[4]\n"
},
"tools.coffee": {
"content": "###\nTools\n\nBasic idea is that there are various tools (instrument, eraser, selection, pattern palette, etc.)\n\nEach tool can respond to down, move, up, actions in the music staff.\n\nStatus: Active\n###\n\n{\n emptyElement\n noteName\n patternSampleNotes\n quantize\n staffNoteToPitch\n} = require \"./lib/util\"\n\ncontext = require \"./lib/audio-context\"\n\nSample = require \"./sample\"\n\ntools = [{\n # Instrument tool\n down: (self, beatNote, e, staffPos) ->\n [beat, staffNote, accidental] = beatNote\n if e.which is 3\n e.preventDefault()\n tools[1].down(self, [beat, staffNote], e, staffPos)\n return\n\n if e.target.tagName is \"VIEWPORT\" or !self.beatNoteInRange(beatNote)\n # Adjust playhead\n self.setPlayHead(quantize(beat, 1))\n return\n\n # Add Note to Score\n instrument = self.activeInstrument()\n\n # Can't add outside of section\n if beat >= self.length()\n return\n\n self.addNote [beat, staffNote, accidental, instrument]\n\n context.resume()\n [section] = self.sectionAt(beat)\n keySignature = section?.keySignature()\n self.playNote instrument, staffNoteToPitch staffNote, accidental, keySignature\n move: ->\n up: ->\n}, {\n # Eraser tool\n down: (self, [beat, staffNote], e, {t}) ->\n if self.removeNote [beat, staffNote], {at: t, within: 0.25}\n self.playBuffer Sample.fx?.eraser, 1, 0\n move: ->\n up: ->\n cursor: \"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAAIdJREFUeJzNUsERwCAIw15n031wDt0Hl0s/9VoF9NnmZzRBCERfI2zusdOtDABmopRGVoRCrdviADNMiADM6L873Mql2NYiw3E2WItzVi2dSuw8JBHNvQyegcU4vmjNFesWZrHFTSlYQ/RhRDgatKZFnXPy7zMIoVaYa3fH5i3PTHira4r/gQv1W1E4p9FksQAAAABJRU5ErkJggg==) 8 8, default\"\n}, {\n # Selection Tool\n down: (self, beatNote) ->\n self.startSelection(beatNote)\n move: (self, beatNote, e) ->\n self.modifySelection(beatNote, e)\n up: (self, beatNote) ->\n self.endSelection(beatNote)\n cursor: \"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAiUlEQVRYR+1XQQrAIAyr/3/0RLAyqoUdzNJDvFnEpkkM2MzssX21Qw1SGo0igHfNgfiZ6/sI4HqDSVs6wGlaCNXZpQ7gN80jkIxyNAtLkjIA0BOn99O0d0RlJIjvFC3JZkJ0Q3kgMqAcWIwoB8oEkXKAHsV0APIAXQI6AHkA9vf78jUbZ+gAaB7opmM7IWnG3nUAAAAASUVORK5CYII=) 0 0, default\"\n}, {\n # Pattern Tool\n down: (self, beatNote) ->\n [beat, offset] = beatNote\n\n unless self.beatNoteInRange(beatNote)\n # Adjust playhead\n self.setPlayHead(quantize(beat, 1))\n return\n\n sampleNotes = patternSampleNotes self.activePattern(), offset\n\n self.addPattern(beatNote, self.activePattern())\n\n # Play first n unique instrument-note combos\n sampleNotes.forEach ([i, p]) ->\n self.playNote i, p\n\n move: (self, beatNote) ->\n {x, y} = self.beatNoteToStaffPosition(beatNote)\n\n Object.assign self.patternPreviewElement.style,\n left: \"#{x}px\"\n top: \"#{y - 240}px\"\n\n up: (self, beatNote) ->\n cursor: \"default\"\n}]\n\nmodule.exports = (I, self) ->\n defaults I,\n activeInstrument: 0\n activePatternIndex: 0\n activeToolIndex: 0\n\n self.attrObservable \"activeInstrument\", \n \"activePatternIndex\",\n \"activeToolIndex\"\n\n hw =\n x: 194/8\n y: 12\n\n selectionStart = null\n selectionRange = null\n selectionActionVertical = null\n selectionActionHorizontal = null\n selectedNotes = null\n\n self.extend\n activeTool: ->\n tools[self.activeToolIndex()]\n\n activePattern: ->\n if self.activeToolIndex() != 3\n return null\n\n self.patterns()[self.activePatternIndex()]\n\n # Hotkeys when pressed inside an input (ie tempo or length)\n inputHotkeys: (e) ->\n return if e.defaultPrevented\n\n {key} = e\n\n switch key\n when \"Enter\"\n e.preventDefault()\n self.playFromStart()\n when \" \"\n e.preventDefault()\n self.pause()\n\n # Selection\n selectionClass: Observable \"\"\n\n startSelection: (beatNote) ->\n self.selectionClass \"\"\n selectedNotes = null\n selectionStart = beatNote\n selectionRange = selectionToRange(selectionStart, selectionStart)\n updateSelectionElement()\n\n modifySelection: (beatNote, e) ->\n return unless selectionStart\n\n # Auto-scroll when selecting near the edges\n {clientWidth} = document.documentElement\n if e.pageX > 0.95 * clientWidth\n self.autoscrolling(8)\n else if e.pageX < 0.05 * clientWidth\n self.autoscrolling(-8)\n else\n self.autoscrolling(0)\n\n start = self.beatNoteToStaffPosition(selectionStart)\n {x, y} = self.beatNoteToStaffPosition(beatNote)\n\n if x < start.x\n selectionActionHorizontal = \"l\"\n else\n selectionActionHorizontal = \"r\"\n\n if y < start.y\n selectionActionVertical = \"t\"\n else\n selectionActionVertical = \"b\"\n\n selectionRange = selectionToRange(selectionStart, beatNote)\n self.notePosition rangeToText selectionRange\n updateSelectionElement()\n\n endSelection: (beatNote) ->\n return unless selectionStart\n self.autoscrolling(0)\n selectionStart = null\n self.selectionClass \"set #{selectionActionVertical} #{selectionActionHorizontal}\"\n # Selection actions are displayed for operator to continue\n\n selectionReset: ->\n selectionRange = null\n selectionStart = null\n selectedNotes = null\n self.selectionClass \"\"\n Object.assign self.selectionElement.style,\n left: \"-150px\"\n width: 0\n height: 0\n\n selectionCopy: ->\n return unless selectionRange\n\n self.copyRange(selectionRange)\n self.selectionReset()\n\n selectionCut: ->\n return unless selectionRange\n\n self.copyRange(selectionRange)\n self.deleteRange(selectionRange)\n self.selectionReset()\n\n selectionDelete: ->\n return unless selectionRange\n\n self.deleteRange(selectionRange)\n self.selectionReset()\n\n # Expose for testing\n _setSelectedNotes: (notes) ->\n selectedNotes = notes\n\n selectionMove: (beatDelta, staffDelta) ->\n return unless selectionRange\n return if selectionStart # Selection isn't finalized\n\n if !selectedNotes\n selectedNotes = self.song().notesWithinRange(selectionRange)\n\n self.moveNotes(selectedNotes, beatDelta, staffDelta)\n\n selectionRange[0][0] += beatDelta\n selectionRange[0][1] += beatDelta\n selectionRange[1][0] += staffDelta\n selectionRange[1][1] += staffDelta\n\n updateSelectionElement()\n self.adjustScroll beatDelta\n\n navigateLeft: ->\n if selectionRange\n unit = self.quantize() or 0.25\n self.selectionMove(-unit, 0)\n else\n # TODO: Measure, not just 4\n self.adjustPlayhead -4, 4\n\n navigateRight: ->\n if selectionRange\n unit = self.quantize() or 0.25\n self.selectionMove(unit, 0)\n else\n # TODO: Measure, not just 4\n self.adjustPlayhead 4, 4\n \n navigatePageUp: ->\n # TODO: 4 x measure length\n self.adjustPlayhead -16, 16\n\n navigatePageDown: ->\n # TODO: 4 x measure length\n self.adjustPlayhead 16, 16\n\n selectionMoveUp: ->\n self.selectionMove(0, 1)\n\n selectionMoveDown: ->\n self.selectionMove(0, -1)\n\n navigateHome: ->\n self.setPlayHead 0\n\n navigateEnd: ->\n self.setPlayHead self.song().length()\n self.scrollTo 999999\n\n self.addHotkey \"esc\", ->\n self.selectionReset()\n\n # Hotkeys\n self.addHotkey \"space\", \"pause\"\n self.addHotkey \"enter\", \"playFromStart\"\n\n # Selection\n self.addHotkey \"del\", \"selectionDelete\"\n self.addHotkey \"c\", \"selectionCopy\"\n self.addHotkey \"x\", \"selectionCut\"\n self.addHotkey \"s\", ->\n self.activeToolIndex 2\n\n # Navigation / Selection\n # Arrow keys move selection if a selection is active, otherwise scroll one\n # measure\n self.addHotkey \"left\", \"navigateLeft\"\n self.addHotkey \"right\", \"navigateRight\"\n self.addHotkey \"up\", \"selectionMoveUp\"\n self.addHotkey \"down\", \"selectionMoveDown\"\n\n self.addHotkey \"pageup\", \"navigatePageUp\"\n self.addHotkey \"pagedown\", \"navigatePageDown\"\n\n self.addHotkey \"home\", \"rewind\"\n self.addHotkey \"end\", \"navigateEnd\"\n\n # Eraser\n self.addHotkey \"e\", ->\n self.activeToolIndex(1)\n\n # Undo / Redo\n self.addHotkey [\"ctrl+z\", \"meta+z\"], \"undo\"\n self.addHotkey [\"ctrl+y\", \"meta+y\"], \"redo\"\n\n # Save / Load\n self.addHotkey [\"ctrl+s\", \"meta+s\"], \"saveFile\"\n self.addHotkey [\"ctrl+o\", \"meta+o\"], \"openFile\"\n self.addHotkey [\"ctrl+r\", \"meta+r\"], \"exportAudio\"\n\n # Help / About\n self.addHotkey [\"f1\"], \"about\"\n self.addHotkey \"?\", \"showHelp\"\n\n # Select instruments and patterns\n [1..9].forEach (i) ->\n n = i - 1\n\n self.addHotkey i.toString(), ->\n self.activeInstrument n\n self.activeToolIndex(0)\n\n self.addHotkey \"shift+#{i}\", ->\n if self.patterns().length > n\n self.activePatternIndex n\n self.activeToolIndex(3)\n\n self.addHotkey \"0\", ->\n self.activeInstrument 9\n self.activeToolIndex(0)\n\n # Instruments 10-19\n [0..9].forEach (i) ->\n self.addHotkey \"` #{i}\", ->\n n = i + 10\n self.activeInstrument n\n self.activeToolIndex(0)\n\n first = true\n Observable ->\n toolIndex = self.activeToolIndex()\n n = self.activeInstrument()\n\n if toolIndex is 0 and n? and !first\n # Hack to drop observable context for playNote\n # \n setTimeout ->\n self.playNote(n)\n , 0\n\n first = false\n\n # Observe activePattern to populate preview and play note\n Observable ->\n pattern = self.activePattern()\n\n return unless self.patternPreviewElement\n\n emptyElement self.patternPreviewElement\n\n if pattern\n # Hack to drop observable context for playNote\n setTimeout ->\n patternSampleNotes(self.activePattern()).forEach ([i, p]) ->\n self.playNote i, p\n pattern.notes().forEach (note) ->\n self.patternPreviewElement.appendChild self.renderNote(note)\n\n updateSelectionElement = ->\n [[startBeat, endBeat], [startNote, endNote]] = selectionRange\n {x, y:y2} = self.beatNoteToStaffPosition([startBeat, startNote])\n {x:x2,y} = self.beatNoteToStaffPosition([endBeat, endNote])\n \n width = x2 - x\n height = y2 - y\n \n # Selection is inclusive so show around the area by expanding 1/2 beat \n # quantum and 1/2 staff note in each dimension\n Object.assign self.selectionElement.style,\n top: \"#{y - hw.y}px\"\n left: \"#{x - hw.x}px\"\n width: \"#{width + 2 * hw.x}px\"\n height: \"#{height + 2 * hw.y}px\"\n\n return self\n\nselectionToRange = ([startBeat, startNote], [endBeat, endNote]) ->\n if startBeat > endBeat\n x = endBeat\n endBeat = startBeat\n startBeat = x\n \n if startNote > endNote\n x = endNote\n endNote = startNote\n startNote = x\n\n [[startBeat, endBeat], [startNote, endNote]]\n\nrangeToText = ([[startBeat, endBeat], [startNote, endNote]]) ->\n \"\"\"\n Start: #{(startBeat+1).toFixed(2)}\n Length: #{(endBeat - startBeat).toFixed(2)}\n Range: #{noteName staffNoteToPitch startNote}-#{noteName staffNoteToPitch endNote}\n \"\"\"\n"
},
"lib/pattern.coffee": {
"content": "###\nPattern\n\nHolds a pattern of notes. Used as 'stamps', a person can copy a\nselection of notes and paint them as a group onto the staff.\n\nStatus: Needs review\n###\n\nmodule.exports = (I={}, self=Model(I)) ->\n defaults I,\n notes: []\n\n self.attrAccessor \"notes\"\n\n return self\n"
},
"staff-view.coffee": {
"content": "NoteTemplate = require \"./templates/note\"\nSectionTemplate = require \"./templates/section\"\n\n{\n accidentalToSymbol\n clamp\n composedPath\n noteName\n quantize\n staffNoteToPitch\n} = require \"./lib/util\"\n\n{\n CLEF_OFFSET\n PIXELS_PER_BEAT\n} = require \"./const\"\n\nnoteAnimationHandler = ->\n @classList.remove \"active\"\n @removeEventListener \"animationend\", noteAnimationHandler\n\nanimateNoteElement = (element) ->\n if element.classList.contains \"active\"\n return\n\n element.classList.add \"active\"\n element.addEventListener \"animationend\", noteAnimationHandler\n\n# t is leftmost visible beat in the staff view\nupdateMeasureNumbers = (sectionElement, t, section) ->\n sectionLength = section.length()\n numberElements = sectionElement.querySelectorAll('span.measure-number')\n\n beatsPerMeasure = section.numerator()\n measuresPerNumber = 4\n beatGroup = measuresPerNumber * beatsPerMeasure\n\n t1 = quantize t, beatGroup\n if t1 < t\n t1 += beatGroup\n if t1 < 0\n t1 = 0\n t2 = t1 + beatGroup\n\n if t1 < sectionLength\n numberElements[0].innerText = t1 / beatsPerMeasure + 1\n numberElements[0].style.left = t1 * PIXELS_PER_BEAT + \"px\"\n\n if t2 < sectionLength\n numberElements[1].style.display = \"initial\"\n numberElements[1].innerText = t2 / beatsPerMeasure + 1\n numberElements[1].style.left = t2 * PIXELS_PER_BEAT + \"px\"\n else\n numberElements[1].style.display = \"none\"\n\nmodule.exports = (I={}, self=Model(I)) ->\n defaults I,\n gamut: [-14, 14] # 4 octave range from C2 to C6 in staff coordinates\n quantize: 1/4 # Snap to 16th notes (1/4 quarter notes)\n accidentalModifier: 0\n\n self.attrObservable \"gamut\", \"quantize\", \"accidentalModifier\", \"fxStyle\"\n\n rerenderTriggered = false\n clearNoteCache = false\n scrollLeft = null\n clientWidth = null\n\n beatNoteInRange = ([beat, staffNote]) ->\n [noteMin, noteMax] = self.gamut()\n\n staffNote >= noteMin and staffNote <= noteMax and beat >= 0\n\n staffPositionToBeatNote = ({t, y, a}) ->\n staffNote = positionToStaffNote(y)\n\n beat = quantize(t, self.quantize())\n\n # Beat, Staff Note, Accidental\n return [beat, staffNote, a]\n\n element = require(\"./templates/staff\")\n fxStyle: self.fxStyle\n contextmenu: (e) -> e.preventDefault()\n dragstart: (e) -> e.preventDefault()\n scroll: (e) ->\n {scrollLeft, clientWidth} = element\n rerenderTriggered = true\n mousedown: (e) ->\n # Don't trigger an event when the user is clicking a selection action button\n buttonPressed = composedPath(e.target).some (el) -> \n el.tagName is \"BUTTON\"\n if buttonPressed\n return\n\n sp = eventToStaffPosition(e)\n beatNote = eventToBeatNote(e)\n # Apply the active tool\n self.activeTool().down(self, beatNote, e, sp)\n\n rerenderTriggered = true\n mousemove: (e) ->\n beatNote = eventToBeatNote(e)\n\n if beatNoteInRange beatNote\n [beat, staffNote, accidental] = beatNote\n self.notePosition \"Beat: #{(beat+1).toFixed(2)}\\nNote: #{noteName staffNoteToPitch staffNote, accidental}\"\n else\n self.notePosition \"\"\n\n self.activeTool().move(self, beatNote, e)\n mouseup: (e) ->\n beatNote = eventToBeatNote(e)\n\n self.activeTool().up(self, beatNote, e)\n\n repeatClass: ->\n \"hidden\" unless self.loop()\n\n selectionClass: self.selectionClass\n selectionCopy: self.selectionCopy\n selectionCut: self.selectionCut\n selectionDelete: self.selectionDelete\n selectionReset: self.selectionReset\n selectionMoveUp: self.selectionMoveUp\n selectionMoveDown: self.selectionMoveDown\n selectionMoveLeft: self.navigateLeft\n selectionMoveRight: self.navigateRight\n\n playheadElement = element.querySelector(\"playhead\")\n staffElement = element.querySelector(\"staff\")\n notesElement = staffElement.querySelector(\"notes\")\n self.selectionElement = staffElement.querySelector(\"selection\")\n self.patternPreviewElement = staffElement.querySelector(\"pattern-preview\")\n \n bufferStartElement = element.querySelector(\"playhead.buffer-start\")\n bufferEndElement = element.querySelector(\"playhead.buffer-end\")\n\n setTimeout ->\n {scrollLeft, clientWidth} = element\n\n window.addEventListener \"resize\", (e) ->\n {scrollLeft, clientWidth} = element\n rerenderTriggered = true\n\n beatToStaffPosition = (t) ->\n PIXELS_PER_BEAT * t + CLEF_OFFSET\n\n staffPositionToBeat = (x) ->\n (x - CLEF_OFFSET) / PIXELS_PER_BEAT\n\n eventToStaffPosition = (e) ->\n {pageX, pageY, shiftKey, ctrlKey, metaKey} = e\n {top, left} = staffElement.getBoundingClientRect()\n\n # First beat is offset to allow space for the clefs and key signature\n x = pageX - left\n y = pageY - top\n t = staffPositionToBeat(x)\n\n a = self.accidentalModifier()\n\n if a is 0\n if shiftKey\n a = 1\n else if ctrlKey or metaKey\n a = -1\n else if a is 1 and (ctrlKey or metaKey)\n a = 0\n else if a is -1 and shiftKey\n a = 0\n\n {t, y, a}\n\n eventToBeatNote = (e) ->\n staffPositionToBeatNote eventToStaffPosition(e)\n\n positionToStaffNote = (y) ->\n # Seven notes per octave, lines are 48px apart so position is divided\n # by 24\n return Math.round (337 - y) / 24\n\n staffNoteToPosition = (n) ->\n (337 - 24 * n)\n\n renderNote = (datum) ->\n [beat, staffNote, accidental, instrument] = datum\n a = accidentalToSymbol(accidental)\n\n return NoteTemplate\n note: noteName staffNoteToPitch staffNote\n accidental: a\n instrument: \"i#{instrument}\"\n style:\n top: \"#{staffNote * -24 - 24 + 240}px\"\n left: \"#{(beat) * PIXELS_PER_BEAT - 24}px\"\n\n renderSection = (section, startBeat) ->\n el = sectionElementCache.get(section)\n\n if !el\n el = SectionTemplate\n c1: ->\n sig = section.keySignature()\n if sig > 0\n \"s\"\n else if sig < 0\n \"f\"\n c2: ->\n @c1() + Math.abs(section.keySignature())\n meterClass: ->\n n = section.numerator()\n\n \"n#{n}\"\n\n sectionElementCache.set(section, el)\n\n length = section.length()\n Object.assign el.style,\n left: \"#{startBeat * PIXELS_PER_BEAT}px\"\n width: \"#{length * PIXELS_PER_BEAT}px\"\n\n if !el.parentElement\n notesElement.appendChild el\n\n return el\n\n autoscroll = 0\n adjustScroll = 0\n recenterPlayhead = false\n\n self.samples.observe ->\n self.setCursor()\n\n self.activeInstrument.observe (instrument) ->\n self.setCursor()\n\n self.activeToolIndex.observe ->\n self.setCursor()\n\n noteElementCache = new Map\n sectionElementCache = new Map\n\n self.extend\n autoscrolling: (v) ->\n autoscroll = v\n scrollTo: (x) ->\n element.scrollLeft = x\n adjustScroll: (dt) -> # dt in beats\n adjustScroll = dt\n recenterPlayhead: ->\n recenterPlayhead = true\n beatNoteInRange: beatNoteInRange\n beatNoteToStaffPosition: ([beat, staffNote]) ->\n x: beatToStaffPosition(beat)\n y: staffNoteToPosition staffNote\n staffElement: element\n\n performDraw: ->\n if autoscroll\n element.scrollLeft += autoscroll * PIXELS_PER_BEAT / 60\n\n p = Math.floor beatToStaffPosition(self.playTime())\n playheadElement.style.left = p + \"px\"\n\n staffElement.style.width = CLEF_OFFSET + PIXELS_PER_BEAT * self.song().length() + \"px\"\n\n if self.playing() or recenterPlayhead\n # Center playhead when scrolling\n element.scrollLeft = p - clientWidth / 2 + 56\n else if adjustScroll # One time scroll adjustment\n element.scrollLeft += adjustScroll * PIXELS_PER_BEAT\n\n if rerenderTriggered\n rerenderTriggered = false\n self.rerenderNotes()\n\n adjustScroll = 0\n recenterPlayhead = false\n return\n\n triggerRerender: (force) ->\n rerenderTriggered = true\n if force\n clearNoteCache = true\n renderNote: renderNote\n \n # Renders all notes and sections\n rerenderNotes: ->\n x0 = scrollLeft - 48 - 48# 48px margin 48px max note width\n x1 = x0 + clientWidth + 96\n\n t0 = staffPositionToBeat x0\n t1 = staffPositionToBeat x1\n\n if clearNoteCache\n Array.from(noteElementCache.keys()).filter (note) ->\n noteElement = noteElementCache.get(note)\n noteElement.remove()\n noteElementCache.delete(note)\n clearNoteCache = false\n\n # Chunked rendering by section\n sectionStart = 0\n sectionsToRender = new Set\n self.sections().forEach (section) ->\n sectionsToRender.add section\n renderSection(section, sectionStart)\n sectionLength = section.length()\n\n # update section measure numbers?\n # render two measure numbers, leapfrogging and updating the based on\n # scroll offset\n updateMeasureNumbers(sectionElementCache.get(section), t0 - sectionStart, section)\n sectionStart += sectionLength\n\n Array.from(sectionElementCache.keys()).forEach (section) ->\n unless sectionsToRender.has(section)\n el = sectionElementCache.get(section)\n el.remove()\n sectionElementCache.delete(section)\n\n notesToRender = new Set\n self.upcomingNotes t0, t1 - t0, (note, section) ->\n notesToRender.add note\n\n # Add notes that should be within the visible area\n unless noteElementCache.has(note)\n noteElement = renderNote(note)\n sectionElement = sectionElementCache.get(section)\n sectionElement.appendChild noteElement\n noteElementCache.set(note, noteElement)\n\n # Remove notes outside of the visible area\n notesToRemove = Array.from(noteElementCache.keys()).filter (note) ->\n unless notesToRender.has(note)\n noteElement = noteElementCache.get(note)\n noteElement.remove()\n noteElementCache.delete(note)\n\n animateNoteElements: (t, dt) ->\n # Trigger playhead animations\n notes = self.upcomingNotes t, dt, (note) ->\n el = noteElementCache.get(note)\n if el\n animateNoteElement(el)\n\n activeSample: ->\n self.samples()[self.activeInstrument()]\n setCursor: ->\n if self.activeToolIndex() is 0\n if sample = self.activeSample().I\n document.body.style.cursor = sample.cursor\n else # Eraser\n document.body.style.cursor = self.activeTool().cursor\n"
},
"templates/patterns.jadelet": {
"content": "patterns\n @patterns\n"
},
"data/templates/patterns.coffee": {
"content": "module.exports =\n patterns: []\n"
},
"lib/test/briefcase.coffee": {
"content": ""
},
"data/templates/about.coffee": {
"content": ""
},
"lib/drop.coffee": {
"content": "###\nHandle drop events. Will be replaced by system app drop event handling.\n\nStatus: Deprecated.\n###\n\nstopFn = (event, handler) ->\n event.stopPropagation()\n event.preventDefault()\n return false\n\nDrop = (el, handler) ->\n el = document.documentElement\n el.addEventListener \"dragenter\", stopFn\n el.addEventListener \"dragover\", stopFn\n el.addEventListener \"dragleave\", stopFn\n el.addEventListener \"drop\", (event) ->\n stopFn(event)\n handler event.dataTransfer.files\n\nmodule.exports = Drop\n"
},
"lib/read-file.coffee": {
"content": "module.exports = ({accept, change}) ->\n accept ?= \"\"\n\n input = document.createElement 'input'\n input.type = \"file\"\n input.setAttribute \"accept\", accept\n\n input.onchange = ->\n reader = new FileReader()\n\n file = input.files[0]\n change(file) if file\n input.value = null\n\n return input\n"
},
"templates/help.jadelet": {
"content": "about\n @hotkeysTableElement\n"
},
"CHANGELOG.md": {
"content": "Changes\n=======\n\n0.4.5\n-----\n\n- Key Signatures!\n- Toggle Repeat\n- Load from sharable URL\n- Fixed Button Focus Color\n- Fixed bug where active instrument played during section change\n\n0.4.4\n-----\n\n- Features\n - New Sprites\n - Animations\n - Centered scroll during playback\n - Background scrolls now\n - Multi-section Arrangements\n - Reduced beat width so higher tempo songs don't scroll super fast and cause\n eye bleeds.\n\n- Bug Fixes\n - Instrument cursor sometimes was offset on initial load\n - Clear song now maintains tempo, FX\n - Stopping playback now fades out queued long sounds quickly\n - Selection move no longer clumps notes\n - Selection move adjusts scroll position when moving left/right\n\n0.4.3\n-----\n\n- Offline Support\n\n0.4.2\n-----\n\n- Adjustable Quantization\n- Stereophonic Sound\n- Global Effects Presets\n- Demo Songs\n- Keep audio playing when window isn't in focus\n- Fixed bug where audio would cut out rarely\n- Time Signatures\n\n0.4.1\n-----\n\n- Three New instruments (horn, nylon guitar, alternate snare).\n- Clicking scrollbar no longer places a note on smaller screens.\n- MIDI input support\n- Move Selections\n\n0.4.0\n-----\n\n- Downloadable Version\n- Fixed F pitch\n- Quieter Eraser Sound\n\n0.3.0\n-----\n\n### Major Features\n\n- All New Staff View!\n- Save and load to local files\n- Undo / Redo\n- Selection tool!\n- More Instruments!\n\n### Improvements\n\n- Documentation\n - About Page\n - Shortcuts List\n - Donation Link\n - Discord Link\n- Keyboard Shortcuts\n- Favicon\n- Caching and Optimization for web resources\n- Clear All Button\n- Larger Octave Scale\n- Audio Timing Accuracy improved\n- Improved Cross Browser Support\n - Download / Export links now work on Safari, Firefox, and Opera\n\n0.2.0\n-----\n\n- Export .wav, .mp3\n"
},
"lib/clipboard-copy.coffee": {
"content": "###\r\nCopy to clipboard.\r\n\r\nMove to system lib?\r\n\r\nStatus: Done\r\n###\r\nmodule.exports = (str) ->\r\n el = document.createElement 'textarea'\r\n el.value = str\r\n el.setAttribute 'readonly', ''\r\n el.style.position = 'absolute' \r\n el.style.left = '-9999px'\r\n document.body.appendChild el\r\n\r\n el.select()\r\n document.execCommand('copy')\r\n\r\n document.body.removeChild(el)\r\n"
},
"templates/publish.jadelet": {
"content": ".publish\n h1 Published!\n\n p.status @status\n\n pre @value\n\n actions\n button(click=@copy) Copy Link\n button(click=@done) Done\n"
},
"data/templates/publish.coffee": {
"content": ""
},
"data/templates/login.coffee": {
"content": ""
},
"data/templates/help.coffee": {
"content": ""
},
"data/templates/donate.coffee": {
"content": ""
},
"templates/pattern.jadelet": {
"content": "pattern(@class @click)\n preview\n @notes\n"
},
"templates/pattern.coffee": {
"content": ""
},
"views/tools.coffee": {
"content": "ToolsTemplate = require \"../templates/tools\"\nSampleToolTemplate = require \"../templates/sample-tool\"\n\n{Observable, Modal} = system.ui\n\nmodule.exports = (editor) ->\n self =\n toggleToolsOpen: ->\n if self.toolsClass() is \"open\"\n self.toolsClass(\"\")\n else\n self.toolsClass(\"open\")\n\n toolsClass: Observable \"\"\n\n eraser: -> \n editor.activeToolIndex(1)\n eraserActive: -> \n \"active\" if editor.activeToolIndex() is 1\n selection: -> \n editor.activeToolIndex(2)\n selectionActive: ->\n \"active\" if editor.activeToolIndex() is 2\n sampleTools: ->\n editor.samples.map (sample, i) ->\n SampleToolTemplate\n index: \"i#{i}\"\n active: ->\n \"active\" if editor.activeInstrument() is i and editor.activeToolIndex() is 0\n click: ->\n editor.activeInstrument(i)\n editor.activeToolIndex(0)\n element: null\n\n self.element = ToolsTemplate self\n\n return self\n"
},
"data/views/tools.coffee": {
"content": "{Observable} = system.ui\n\nmodule.exports =\n activeToolIndex: Observable\n samples: []\n"
},
"templates/sample-tool.jadelet": {
"content": "tool(class=@index class=@active @click)\n"
},
"data/templates/pattern.coffee": {
"content": ""
},
"views/patterns.coffee": {
"content": "PatternsTemplate = require \"../templates/patterns\"\nPatternTemplate = require \"../templates/pattern\"\n\nPatternPresenter = (pattern, i) ->\n PatternTemplate\n class: ->\n \"active\" if player.activePatternIndex() is i and player.activeToolIndex() is 3\n\n click: -> \n player.activeToolIndex(3)\n player.activePatternIndex(i)\n\n notes: ->\n pattern.notes().filter(([t]) -> t <= 4).map player.renderNote\n\nmodule.exports = (player) ->\n element: PatternsTemplate\n patterns: ->\n player.patterns.map PatternPresenter\n"
},
"templates/option.jadelet": {
"content": "option(@value) @text\n"
},
"lib/test/s3-fs.coffee": {
"content": ""
},
"lib/test/read-file.coffee": {
"content": ""
},
"lib/test/pattern.coffee": {
"content": ""
},
"lib/test/mp3-worker.coffee": {
"content": ""
},
"workspaces/size.coffee": {
"content": "items = Object.keys(PACKAGE.source).map (name) ->\n [name, PACKAGE.source[name].content.length]\n.sort (a, b) ->\n b[1] - a[1]\n.map ([name, size]) ->\n size.toString().padStart(10) + \" \" + name\n.join(\"\\n\")\n\npre = document.createElement \"pre\"\npre.innerText = items\npre.style.overflow = \"auto\"\n\ndocument.body.appendChild pre\n"
},
"data/views/size.coffee": {
"content": ""
},
"templates/button.jadelet": {
"content": "button(@click @class) @text\n"
},
"templates/exit-button.jadelet": {
"content": "button(@click) @text\n"
},
"templates/stripe/form.jadelet": {
"content": "form(action=\"/charge\" method=\"post\" @submit)\n input(type=\"hidden\" name=\"stripeToken\" value=@tokenId)\n"
},
"lib/test/stripe.coffee": {
"content": ""
},
"lib/stripe-elements.coffee": {
"content": "stripe = Stripe('pk_znR9dUa0sPXSlVv2009vpWdtexnnq')\n\nelements = stripe.elements()\n\n# Custom styling can be passed to options when creating an Element.\n# (Note that this demo uses a wider set of styles than the guide below.)\nstyle =\n base:\n color: '#32325d'\n fontFamily: '\"Helvetica Neue\", Helvetica, sans-serif'\n fontSmoothing: 'antialiased'\n fontSize: '16px'\n '::placeholder':\n color: '#aab7c4'\n invalid:\n color: '#fa755a'\n iconColor: '#fa755a'\n\n\n# Create an instance of the card Element.\ncard = elements.create 'card',\n style: style\n\n# Add an instance of the card Element into the `card-element` <div>.\ncard.mount('#card-element')\n\n# Handle real-time validation errors from the card Element.\ncard.addEventListener 'change', (event) ->\n displayError = document.getElementById('card-errors')\n if event.error\n displayError.textContent = event.error.message\n else\n displayError.textContent = ''\n\n\n# Handle form submission.\nform = document.getElementById('payment-form')\nform.addEventListener 'submit', (event) ->\n event.preventDefault()\n\n stripe.createToken(card).then (result)->\n if result.error\n # Inform the user if there was an error.\n errorElement = document.getElementById('card-errors')\n errorElement.textContent = result.error.message\n else\n # Send the token to your server.\n stripeTokenHandler(result.token)\n\n\n# Submit the form with the token ID.\nstripeTokenHandler = (token) ->\n # Insert the token ID into the form so it gets submitted to the server\n\n # Submit the form\n form.submit()\n\n"
},
"lib/stripe-checkout.coffee": {
"content": "# dev\r\n# stripe = Stripe 'pk_znR9dUa0sPXSlVv2009vpWdtexnnq'\r\n# CHECKOUT_API = \"https://x09r00vyqe.execute-api.us-east-1.amazonaws.com/dev\"\r\n\r\n# prod\r\ntry\r\n stripe = Stripe 'pk_PPCRZgLrovwFHSKmMkjtVONHDs3pR'\r\n\r\nCHECKOUT_API = \"https://api.danielx.net\"\r\nproductId = 1\r\n\r\n{Modal} = system.ui\r\n\r\nresult = window.location.search.match /^\\?checkout_session_id=(.*)/\r\n\r\nif result\r\n checkoutId = result[1]\r\n\r\n Modal.alert \"\"\"\r\n Thank you for your purchase, check your email for the download link!\r\n \"\"\"\r\n\r\nmodule.exports =\r\n # Create a session on stripe checkout, then redirect to it with\r\n # the session id\r\n #\r\n # Eventually there will be more product ids, but right now they are\r\n # all Paint Composer\r\n # When the checkout is compeleted stripe hits the stripe-webhook lambda\r\n # which sends the user an email with links to download.\r\n purchase: ->\r\n fetch(\"#{CHECKOUT_API}/purchases/#{productId}\")\r\n .then (r) -> r.json()\r\n .then (data) ->\r\n console.log data\r\n\r\n return data.id\r\n .then (sessionId) ->\r\n stripe.redirectToCheckout\r\n sessionId: sessionId\r\n .then (result) ->\r\n if result.error?\r\n console.error result.error\r\n .catch ->\r\n Modal.alert \"\"\"\r\n I AM ERROR\r\n \"\"\"\r\n"
},
"lib/test/stripe-elements.coffee": {
"content": ""
},
"lib/test/stripe-checkout.coffee": {
"content": ""
},
"templates/buy-now.jadelet": {
"content": "section.purchase\n iframe(src=\"https://store.steampowered.com/widget/1208550/?t=If%20you%20enjoy%20Paint%20Composer%20please%20purchase%20the%20full%20version%21%20Features%20include%20multi-section%20arrangements%2C%20key%20signatures%2C%20and%20more%21%20Your%20support%20truly%20matters%20to%20me%20as%20an%20independent%20creator.%20The%20game%20is%20in%20active%20development%20so%20keep%20checking%20back%20for%20the%20latest%20updates%2C%20thank%20you%21\" frameborder=\"0\" width=\"646\" height=\"190\")\n"
},
"data/templates/buy-now.coffee": {
"content": ""
},
"lib/test/midi.coffee": {
"content": ""
},
"templates/note-control.jadelet": {
"content": "aside.note-control\n section.accidental\n label Sharp / Flat\n button(click=@natural class=@naturalActive) ♮\n button(click=@sharp class=@sharpActive) ♯\n button(click=@flat class=@flatActive) ♭\n section.snap\n label Snap\n button(click=@quarter class=@quarterActive) ♩\n button(click=@eight class=@eigthActive) ♪\n button(click=@sixteenth class=@sixteenthActive) ♬\n button.triplet(click=@triplet class=@tripletActive) 3\n input(value=@quantizeInput)\n"
},
"data/views/patterns.coffee": {
"content": ""
},
"views/note-control.coffee": {
"content": "###\nNote Control handles quantization, accidentals.\n\nLater will handle duration, dynamics as well.\n\nViews connect the UI to the model.\n###\n\n{Observable} = system.ui\n\nTemplate = require \"../templates/note-control\"\n\nmodule.exports = (model={}) ->\n model.accidentalModifier ?= Observable 0\n model.quantize ?= Observable 1/4\n\n # View State\n tripletMode = Observable false\n\n self =\n accidentalModifier: model.accidentalModifier\n quantize: model.quantize\n\n natural: ->\n self.accidentalModifier 0\n sharp: ->\n self.accidentalModifier 1\n flat: ->\n self.accidentalModifier -1\n\n naturalActive: ->\n \"active\" if self.accidentalModifier() is 0\n sharpActive: ->\n \"active\" if self.accidentalModifier() is 1\n flatActive: ->\n \"active\" if self.accidentalModifier() is -1\n\n quarter: ->\n if tripletMode()\n self.quantize 2/3\n else\n self.quantize 1\n eight: ->\n if tripletMode()\n self.quantize 1/3\n else\n self.quantize 1/2\n sixteenth: ->\n if tripletMode()\n self.quantize 1/6\n else\n self.quantize 1/4\n triplet: ->\n q = self.quantize()\n if tripletMode()\n tripletMode false\n self.quantize q * 3/2\n else\n tripletMode true\n self.quantize q * 2/3\n\n # Assuming 3/4 4/4 or 5/4 time signatures for now\n quarterActive: ->\n q = self.quantize()\n\n if q is 1 or q is 2/3\n \"active\"\n\n eigthActive: ->\n q = self.quantize()\n\n if q is 1/2 or q is 1/3\n \"active\"\n sixteenthActive: ->\n q = self.quantize()\n\n if q is 1/4 or q is 1/6\n \"active\"\n\n tripletActive: ->\n \"active\" if tripletMode()\n\n quantizeInput: Observable model.quantize._value\n\n element: null\n\n model.quantize.observe (value) ->\n self.quantizeInput value\n \n self.quantizeInput.observe (value) ->\n v = parseFloat(value)\n if isFinite(v)\n self.quantize v\n\n self.element = Template(self)\n\n return self\n"
},
"data/views/note-control.coffee": {
"content": "module.exports = {}"
},
"data/templates/note-control.coffee": {
"content": ""
},
"lib/test/wave-worker.js": {
"content": ""
},
"lib/test/audio-encoder.coffee": {
"content": ""
},
"lib/test/clipboard-copy.coffee": {
"content": ""
},
"lib/test/audio-context.coffee": {
"content": ""
},
"lib/test/channel.coffee": {
"content": ""
},
"lib/legacy/song.coffee": {
"content": "###\nSong\n====\n\nA song has a tempo, a number of channels, and a number of patterns.\n\nPatterns are placed in the channels.\n\nThis is the original song format implementation, we need to keep it around for\nbackward compatibility.\n\nStatus: Legacy\n###\n\n\nChannel = require \"./channel\"\nPattern = require \"./pattern\"\n\nmodule.exports = (I={}, self=Model(I)) ->\n defaults I,\n channels: [{\n data:\n 0: 0\n }, {}, {}, {}]\n patterns: [{}]\n tempo: 90\n\n self.attrObservable \"tempo\"\n\n self.attrModels \"channels\", Channel\n self.attrModels \"patterns\", Pattern\n\n numPatterns = 10\n\n # Init Patterns\n initPatterns = ->\n [0...numPatterns].forEach (n) ->\n self.patterns()[n] ?= Pattern()\n\n # Be sure that our patterns and data are in sync!\n # TODO: Shouldn't need this hack\n self.patterns self.patterns().slice()\n\n initPatterns()\n\n self.extend\n channelPatterns: (n) ->\n self.channels.get(n).patterns(self.patterns())\n\n setPattern: (channel, beat, patternIndex) ->\n self.channels.get(channel).setPattern(beat, patternIndex)\n\n canSet: (channel, beat, patternIndex) ->\n self.channels.get(channel).canSet(beat, patternIndex, self.patterns())\n\n patternAt: (channel, beat) ->\n self.channels.get(channel).patternAt(beat, self.patterns())\n\n patternsDataAt: (beat) ->\n self.channels().map (channel) ->\n channel.patternDataAt(beat, self.patterns())[0]\n\n removePattern: (channel, beat) ->\n self.channels.get(channel).removePattern(beat, self.patterns())\n\n upcomingNotes: (t, dt) ->\n patterns = self.patterns()\n\n self.channels.map (channel) ->\n channel.upcomingNotes t, dt, patterns\n .flat()\n\n size: ->\n Math.max.call null, (self.channels().map (channel) ->\n channel.size(self.patterns())\n )...\n\n fromJSON: (data) ->\n if data.patterns?\n self.patterns data.patterns.map (data) ->\n Pattern(data)\n self.channels data.channels.map (data) -> \n Channel(data)\n else\n self.patterns [0...numPatterns].map (i) ->\n if i is 0\n Pattern\n beats: parseInt data.beats, 10\n notes: data.notes\n else\n Pattern()\n\n self.channels [{\n data:\n 0: 0\n }, {}, {}, {}].map (channelData) ->\n Channel(channelData)\n\n initPatterns()\n\n self.tempo data.tempo\n\n self\n\n return self\n"
},
"lib/test/legacy/song.coffee": {
"content": "Song = require \"/lib/legacy/song\"\nPattern = require \"/lib/legacy/pattern\"\n\ndescribe \"Song\", ->\n it \"Should know its size\", ->\n song = Song\n patterns: [\n beats: 4\n ]\n\n assert.equal song.size(), 4, \"song.size() is #{song.size()}\"\n\n it \"Should know the correct size when the pattern has a different size\", ->\n song = Song\n patterns: [\n beats: 10\n ]\n\n assert.equal song.size(), 10\n\n it \"Should have ten patterns\", ->\n song = Song()\n assert.equal song.toJSON().patterns.length, 10\n\n it \"Should know it's tempo\", ->\n song = Song\n tempo: 54\n\n assert.equal song.tempo(), 54\n\n it \"Should return upcoming notes\", ->\n song = Song()\n\n assert.equal song.upcomingNotes(0, 1).length, 0\n\n describe \"With a single pattern\", ->\n pattern = Pattern\n notes: [0..3].map (i) ->\n [i, 0, 0]\n\n song = Song\n channels: [\n {\n data:\n 0: 0\n }\n ]\n patterns: [\n pattern.I\n ]\n\n it \"should return all the notes\", ->\n assert.equal song.upcomingNotes(0, 1).length, 1\n assert.equal song.upcomingNotes(1, 1).length, 1\n assert.equal song.upcomingNotes(2, 1).length, 1\n assert.equal song.upcomingNotes(3, 1).length, 1\n\n assert.equal song.upcomingNotes(0, 4).length, 4\n\n it \"shouldn't loop past the end\", ->\n assert.equal song.upcomingNotes(4, 1).length, 0\n assert.equal song.upcomingNotes(0, 8).length, 4\n\n describe \"With multiple patterns in parallel\", ->\n pattern1 = Pattern\n notes: [0..3].map (i) ->\n [i, 0, 0]\n\n pattern2 = Pattern\n notes: [0..3].map (i) ->\n [i + 0.5, 0, 1]\n\n song = Song\n channels: [\n {\n data:\n 0: 0\n }, {\n data:\n 0: 1\n }\n ]\n patterns: [\n pattern1.I\n pattern2.I\n ]\n\n it \"should return all the notes\", ->\n assert.equal song.upcomingNotes(0, 1).length, 2\n assert.equal song.upcomingNotes(1, 1).length, 2\n assert.equal song.upcomingNotes(2, 1).length, 2\n assert.equal song.upcomingNotes(3, 1).length, 2\n\n assert.equal song.upcomingNotes(0, 4).length, 8\n\n it \"shouldn't loop past the end\", ->\n assert.equal song.upcomingNotes(4, 1).length, 0\n assert.equal song.upcomingNotes(3, 2).length, 2\n\n describe \"With two patterns in sequence\", ->\n pattern1 = Pattern\n notes: [0..3].map (i) ->\n [i, 0, 0]\n\n song = Song\n channels: [\n {\n data:\n 0: 0\n 4: 0\n }\n ]\n patterns: [\n pattern1.I\n ]\n\n it \"should return all the notes in short timesteps\", ->\n assert.equal song.upcomingNotes(0, 1).length, 1\n assert.equal song.upcomingNotes(1, 1).length, 1\n assert.equal song.upcomingNotes(2, 1).length, 1\n assert.equal song.upcomingNotes(3, 1).length, 1\n\n assert.equal song.upcomingNotes(4, 1).length, 1\n assert.equal song.upcomingNotes(5, 1).length, 1\n assert.equal song.upcomingNotes(6, 1).length, 1\n assert.equal song.upcomingNotes(7, 1).length, 1\n\n it \"should Work with larger timesteps\", ->\n assert.equal song.upcomingNotes(0, 4).length, 4\n assert.equal song.upcomingNotes(4, 4).length, 4\n\n it \"should cross pattern boundries\", ->\n assert.equal song.upcomingNotes(0, 8).length, 8\n\n it \"shouldn't loop past the end\", ->\n assert.equal song.upcomingNotes(8, 1).length, 0\n assert.equal song.upcomingNotes(0, 16).length, 8\n\n it \"should have the correct times for the later notes\", ->\n notes = song.upcomingNotes(0, 16)\n\n assert.equal notes[4][0], 4\n\n notes = song.upcomingNotes(3, 16)\n\n assert.equal notes[1][0], 1\n\n describe \"#canSet\", ->\n it \"should allow placing a pattern if there is space, not if not\", ->\n song = Song\n patterns: [\n beats: 8\n ]\n channels: [\n {\n data:\n 0: 0\n 8: 0\n }\n ]\n\n assert song.canSet(0, 16, 0)\n assert !song.canSet(0, 15, 0)\n\n describe \"#patternsDataAt\", ->\n it \"should get a pattern from each channel\", ->\n song = Song\n patterns: [{\n beats: 8\n }, {\n beats: 8\n }]\n channels: [\n {\n data:\n 0: 0\n }, {\n data:\n 7: 0\n }\n ]\n\n assert.equal song.patternsDataAt(0).filter((x) -> x).length, 1\n assert.equal song.patternsDataAt(7).filter((x) -> x).length, 2\n assert.equal song.patternsDataAt(8).filter((x) -> x).length, 1\n"
},
"lib/legacy/channel.coffee": {
"content": "###\nChannel\n=======\n\nA channel holds a sequence of patterns. The patterns are stored in the `data`\ntable at beat keys with `patternId` values.\n\nStatus: Legacy\n###\n\nmodule.exports = (I={}, self=Model(I)) ->\n defaults I,\n data: {}\n\n patternStarts = (patterns) ->\n Object.keys(I.data).map (start) ->\n start = parseInt(start, 10)\n patternIndex = parseInt(I.data[start], 10)\n pattern = patterns[patternIndex]\n end = start + pattern.size()\n\n [start, end, pattern, patternIndex]\n\n self.extend\n patterns: patternStarts\n\n canSet: (beat, patternIndex, patterns) ->\n size = patterns[patternIndex].size()\n\n toInsert = [beat, beat + size]\n\n # Can't set if there are any overlaps\n !patternStarts(patterns).some (segment) ->\n overlap(toInsert, segment)\n\n patternDataAt: (beat, patterns) ->\n patternStarts(patterns).filter ([start, end]) ->\n start <= beat < end\n\n patternAt: (beat, patterns) ->\n self.patternDataAt(beat, patterns)\n .map(([start, end, pattern, patternIndex]) ->\n patternIndex\n )[0]\n\n setPattern: (beat, patternIndex) ->\n I.data[beat] = patternIndex\n\n removePattern: (beat, patterns) ->\n patternsAtBeat = patternStarts(patterns).filter ([start, end]) ->\n start <= beat < end\n\n patternsAtBeat.forEach ([start]) ->\n delete I.data[start]\n\n return patternsAtBeat.length > 0\n\n upcomingNotes: (t, dt, patterns) ->\n patternStarts(patterns).filter ([start, end, pattern]) ->\n (start <= t < end) or # t is within pattern or\n (t <= start < t + dt) # pattern start is within [t, t + dt)\n .map ([start, end, pattern]) ->\n offset = t - start\n\n pattern.upcomingNotes(offset, dt)\n .flat()\n\n size: (patterns) ->\n patternStarts(patterns).reduce (n, [start, end]) ->\n max n, end\n , 0\n\n return self\n\n\n{min, max} = Math\n\noverlap = ([a1, a2], [b1, b2]) ->\n max(0, min(a2, b2) - max(a1, b1)) > 0\n"
},
"lib/legacy/pattern.coffee": {
"content": "###\nPattern\n\nHolds a pattern of notes. The previous version had a fixed number of patterns\nthat could be placed into channels. This version of the feature remains here to\nnot interfere with any future development. We use patterns in the newer version\nto create \"stamps\" to paint selections of notes onto the staff.\n\nStatus: Legacy \n###\n\nrequire \"../extensions\"\n\nmodule.exports = (I={}, self=Model(I)) ->\n defaults I,\n beats: 8\n notes: []\n\n I.beats = Number(I.beats) # Force number\n\n if isNaN(I.beats)\n throw new Error \"Beats must be a number\"\n\n self.attrObservable \"beats\"\n self.attrAccessor \"notes\"\n\n Object.assign self,\n addNote: (note) ->\n I.notes.push(note)\n\n removeNote: ([time, note]) ->\n matched = I.notes.filter ([t, n]) ->\n time is t and note is n\n\n self.notes().remove matched.last()\n\n size: ->\n self.beats()\n\n # `t` and `dt` are in beats.\n upcomingNotes: (t, dt) ->\n self.notes().filter ([time]) ->\n if dt > 0\n t <= time < t + dt\n else if dt < 0\n t + dt < time <= t\n .map ([time, note, instrument]) ->\n [time - t, note, instrument]\n"
},
"lib/test/legacy/pattern.coffee": {
"content": ""
},
"lib/test/legacy/channel.coffee": {
"content": ""
},
"lib/test/gist.coffee": {
"content": ""
},
"lib/legacy/gist.coffee": {
"content": "###\nGithub gist loading and saving. Github no longer allows saving anonymous gists\nso this is only used for loading the previously saved ones.\n\nStatus: Legacy\n###\n\n\nbase = \"https://api.github.com/gists\"\n\nmodule.exports =\n load: (gistId) ->\n # b60b30d55133c4073e8c1846b0b217f8\n fetch(\"#{base}/#{gistId}\").then (data) ->\n data.json()\n .then (data) ->\n data = data.files[\"data.json\"]?.content or data.files[\"pattern0.json\"]?.content\n\n if data\n JSON.parse data\n else\n alert \"Failed to load gist with id: #{gistId}\"\n"
},
"lib/test/legacy/gist.coffee": {
"content": "Gist = require \"/lib/legacy/gist\"\n\ndescribe \"Gist\", -> \n it \"should load gists\", ->\n Gist.load \"fec8cabc6433099dbe22\"\n"
},
"lib/test/hotkeys.coffee": {
"content": ""
},
"lib/test/drop.coffee": {
"content": ""
},
"lib/test/model.coffee": {
"content": ""
},
"lib/test/mousetrap.js": {
"content": ""
},
"lib/midi-input.coffee": {
"content": "###\nMIDI Input\n\nConnect and map MIDI input into notes on the musical staff.\n\nStatus: Active\n###\n\ncontext = require \"./audio-context\"\n{pitchToStaffNote, quantize} = require \"./util\"\n\nmodule.exports = (I, self) ->\n try # Firefox is shitting the bed on this one\n navigator.requestMIDIAccess()\n .then (midiAccess) ->\n Array.from(midiAccess.inputs.values()).forEach (input) ->\n input.onmidimessage = ({data}) ->\n [f, note, velocity] = data\n \n # Channel 1 Down\n if f is 0x90\n pitch = note - 60\n [staffNote, accidental] = pitchToStaffNote(pitch)\n instrument = self.activeInstrument()\n \n # Add at the quantized playhead time\n quantizedBeat = quantize self.playTime(), 0.25\n self.addNote [quantizedBeat, staffNote, accidental, instrument]\n context.resume()\n self.playNote instrument, pitch\n self.triggerRerender()\n"
},
"lib/test/midi-input.coffee": {
"content": ""
},
"lib/encoder/wave-worker.js": {
"content": "// https://stackoverflow.com/a/42632646/68210\r\n\r\nself.onmessage = function( e ){\r\n var wavPCM = new WavePCM( e['data']['config'] );\r\n wavPCM.record( e['data']['pcmArrays'] );\r\n wavPCM.requestData();\r\n};\r\n\r\nvar WavePCM = function( config ){\r\n this.sampleRate = config['sampleRate'] || 48000;\r\n this.bitDepth = config['bitDepth'] || 16;\r\n this.recordedBuffers = [];\r\n this.bytesPerSample = this.bitDepth / 8;\r\n};\r\n\r\nWavePCM.prototype.record = function( buffers ){\r\n this.numberOfChannels = this.numberOfChannels || buffers.length;\r\n var bufferLength = buffers[0].length;\r\n var reducedData = new Uint8Array( bufferLength * this.numberOfChannels * this.bytesPerSample );\r\n\r\n // Interleave\r\n for ( var i = 0; i < bufferLength; i++ ) {\r\n for ( var channel = 0; channel < this.numberOfChannels; channel++ ) {\r\n\r\n var outputIndex = ( i * this.numberOfChannels + channel ) * this.bytesPerSample;\r\n var sample = buffers[ channel ][ i ];\r\n\r\n // Check for clipping\r\n if ( sample > 1 ) {\r\n sample = 1;\r\n }\r\n\r\n else if ( sample < -1 ) {\r\n sample = -1;\r\n }\r\n\r\n // bit reduce and convert to uInt\r\n switch ( this.bytesPerSample ) {\r\n case 4:\r\n sample = sample * 2147483648;\r\n reducedData[ outputIndex ] = sample;\r\n reducedData[ outputIndex + 1 ] = sample >> 8;\r\n reducedData[ outputIndex + 2 ] = sample >> 16;\r\n reducedData[ outputIndex + 3 ] = sample >> 24;\r\n break;\r\n\r\n case 3:\r\n sample = sample * 8388608;\r\n reducedData[ outputIndex ] = sample;\r\n reducedData[ outputIndex + 1 ] = sample >> 8;\r\n reducedData[ outputIndex + 2 ] = sample >> 16;\r\n break;\r\n\r\n case 2:\r\n sample = sample * 32768;\r\n reducedData[ outputIndex ] = sample;\r\n reducedData[ outputIndex + 1 ] = sample >> 8;\r\n break;\r\n\r\n case 1:\r\n reducedData[ outputIndex ] = ( sample + 1 ) * 128;\r\n break;\r\n\r\n default:\r\n throw \"Only 8, 16, 24 and 32 bits per sample are supported\";\r\n }\r\n }\r\n }\r\n\r\n this.recordedBuffers.push( reducedData );\r\n};\r\n\r\nWavePCM.prototype.requestData = function(){\r\n var bufferLength = this.recordedBuffers[0].length;\r\n var dataLength = this.recordedBuffers.length * bufferLength;\r\n var headerLength = 44;\r\n var wav = new Uint8Array( headerLength + dataLength );\r\n var view = new DataView( wav.buffer );\r\n\r\n view.setUint32( 0, 1380533830, false ); // RIFF identifier 'RIFF'\r\n view.setUint32( 4, 36 + dataLength, true ); // file length minus RIFF identifier length and file description length\r\n view.setUint32( 8, 1463899717, false ); // RIFF type 'WAVE'\r\n view.setUint32( 12, 1718449184, false ); // format chunk identifier 'fmt '\r\n view.setUint32( 16, 16, true ); // format chunk length\r\n view.setUint16( 20, 1, true ); // sample format (raw)\r\n view.setUint16( 22, this.numberOfChannels, true ); // channel count\r\n view.setUint32( 24, this.sampleRate, true ); // sample rate\r\n view.setUint32( 28, this.sampleRate * this.bytesPerSample * this.numberOfChannels, true ); // byte rate (sample rate * block align)\r\n view.setUint16( 32, this.bytesPerSample * this.numberOfChannels, true ); // block align (channel count * bytes per sample)\r\n view.setUint16( 34, this.bitDepth, true ); // bits per sample\r\n view.setUint32( 36, 1684108385, false); // data chunk identifier 'data'\r\n view.setUint32( 40, dataLength, true ); // data chunk length\r\n\r\n for (var i = 0; i < this.recordedBuffers.length; i++ ) {\r\n wav.set( this.recordedBuffers[i], i * bufferLength + headerLength );\r\n }\r\n\r\n self.postMessage( wav, [wav.buffer] );\r\n self.close();\r\n};\r\n"
},
"lib/encoder/index.coffee": {
"content": "###\nEncode web audio buffer data in a variety of formats. Handles both stereo and\nmono.\n\nMP3, WAV\n\nUse web workers where appropriate.\n\nStatus: Acceptable\n###\n\n# \n# channelData: [Int16Array]\n# returns a Promise containing a Blob with type audio/mp3\nmp3Encode = (data) ->\n remoteWorker(\"lame-worker2.js\")\n .then (worker) ->\n new Promise (resolve, reject) ->\n worker.postMessage data\n\n worker.onmessage = (e) ->\n resolve e.data\n\nremoteWorker = (url) ->\n fetch(url)\n .then (response) ->\n response.arrayBuffer()\n .then (buffer) ->\n blob = new Blob [buffer], type: \"application/javascript\"\n URL.createObjectURL(blob)\n .then (url) ->\n # TODO: revokeObjectURL\n new Worker(url)\n\n# Stereo or Mono\naudioBufferToChannelData = (audioBuffer) ->\n channels = audioBuffer.numberOfChannels\n if channels is 2\n [\n audioBuffer.getChannelData(0)\n audioBuffer.getChannelData(1)\n ]\n else\n [audioBuffer.getChannelData(0)]\n\naudioChannelDataToInt16 = (buffer) ->\n {min, max} = Math\n\n bufferLength = buffer.length\n data = new Int16Array bufferLength\n\n i = 0\n while i < bufferLength\n sample = max -1, min 1, buffer[i]\n\n if sample < 0\n sample *= 0x8000\n else\n sample *= 0x7FFF\n\n data[i] = sample\n i++\n\n return data\n\naudioBufferToMP3 = (audioBuffer) ->\n throw new Error \"Must pass an AudioBuffer to encode .mp3\" unless audioBuffer\n\n bufferWorker = fnToWorker(audioChannelDataToInt16)\n \n Promise.all audioBufferToChannelData(audioBuffer).map (channelBuffer) ->\n bufferWorker(channelBuffer)\n .then (channelData) ->\n mp3Encode\n channelData: channelData\n\naudioBufferToWav = (audioBuffer) ->\n throw new Error \"Must pass an AudioBuffer to encode .wav\" unless audioBuffer\n\n new Promise (resolve, reject) ->\n workerSource = new Blob [PACKAGE.distribution[\"lib/encoder/wave-worker\"].content],\n type: \"application/javascript\"\n\n url = URL.createObjectURL(workerSource)\n worker = new Worker(url)\n\n worker.onmessage = (e) ->\n resolve new Blob [e.data.buffer], type: \"audio/wav\"\n URL.revokeObjectURL(url)\n\n worker.postMessage\n pcmArrays: audioBufferToChannelData(audioBuffer)\n config:\n sampleRate: audioBuffer.sampleRate\n\n# Convert a function without any closure variables into a worker source\n# it receives a single parameter from the onmessage event data, passes it to\n# the function, returns the value to the parent, then closes the worker.\nfnToWorker = (fn) ->\n (data) ->\n new Promise (resolve, reject) ->\n workerSource = new Blob [\"\"\"\n self.onmessage = function (e) {\n self.postMessage((#{fn.toString()})(e.data));\n self.close();\n };\n \"\"\"], type: \"application/javascript\"\n \n url = URL.createObjectURL(workerSource)\n worker = new Worker(url)\n\n # Resolev and cleanup when we receive the message from the worker.\n worker.onmessage = (e) ->\n resolve(e.data)\n URL.revokeObjectURL(url)\n\n worker.postMessage data\n\nmodule.exports =\n mp3Encode: mp3Encode\n audioBufferToMP3: audioBufferToMP3\n audioBufferToWav: audioBufferToWav\n audioChannelDataToInt16: audioChannelDataToInt16\n fnToWorker: fnToWorker\n"
},
"lib/test/encoder/index.coffee": {
"content": "{\n audioBufferToMP3\n audioBufferToWav\n audioChannelDataToInt16\n fnToWorker\n} = Encoder = require \"/lib/encoder/index\"\n\n{abs} = Math\n\n# Noise buffer\ncontext = new AudioContext()\nstereoBuffer = context.createBuffer(2, 65536, 44100)\nmonoBuffer = context.createBuffer(1, 65536, 44100)\n\nnumberOfChannels = stereoBuffer.numberOfChannels\nc = 0\nwhile c < numberOfChannels\n buffer = stereoBuffer.getChannelData(c)\n i = 0\n while i < buffer.length\n # white noise in [-1.0, 1.0]\n buffer[i] = Math.random() * 2 - 1\n i++\n c++\n\nbuffer = monoBuffer.getChannelData(0)\ni = 0\nwhile i < buffer.length\n # white noise in [-1.0, 1.0]\n buffer[i] = Math.random() * 2 - 1\n i++\n\ndownloadLink = (blob, name) ->\n link = document.createElement 'a'\n link.textContent = link.download = name\n link.href = URL.createObjectURL(blob)\n document.body.appendChild link\n return\n\ndescribe \"Audio Encoder\", ->\n it \"should encode MP3s in stereo\", ->\n audioBufferToMP3(stereoBuffer)\n\n it \"should encode MP3s in mono\", ->\n audioBufferToMP3(monoBuffer)\n\n it \"should encode wav in stereo\", ->\n audioBufferToWav(stereoBuffer)\n\n it \"should encode wav in mono\", ->\n audioBufferToWav(monoBuffer)\n\n it \"should convert audioBuffer to Int16Array\", ->\n assert audioChannelDataToInt16 monoBuffer.getChannelData(0)\n\n assert audioChannelDataToInt16 stereoBuffer.getChannelData(0)\n assert audioChannelDataToInt16 stereoBuffer.getChannelData(1)\n\n it \"should convert to Int16Array a web worker\", ->\n workerFn = fnToWorker(audioChannelDataToInt16)\n\n workerFn monoBuffer.getChannelData(0)\n\n it \"should have a proper distribution of values when converting to int16\"\n ->\n b = audioChannelDataToInt16(monoBuffer.getChannelData(0))\n\n bins = new Map\n l = b.length\n\n i = 0\n while i < l\n v = b[i] + 0x8000\n c = (bins.get(v)|0)\n bins.set(v, c+1)\n i++\n\n console.log(\"i\", i)\n expected = 0\n actual = 0\n maxDiscrepancy = 0\n maxDiscrepancyIndex = null\n i = 0\n while i < 65536\n c = (bins.get(i)|0)\n expected += 10\n actual += c\n\n d = abs(expected - actual)\n if d > maxDiscrepancy\n maxDiscrepancy = d\n maxDiscrepancyIndex = i\n\n i++\n\n sum = Array.from(bins.values()).reduce (a, b) -> a+b\n \n console.log \"SUM\", sum, actual, expected\n console.log b, bins\n console.log \"Disc\", maxDiscrepancy, maxDiscrepancyIndex\n"
},
"lib/test/encoder/wave-worker.js": {
"content": ""
},
"lib/fx.coffee": {
"content": "###\nFX\n\nAn audio FX output network. Currently experimenting with a fixed network\nwith some params.\n\nStatus: WIP\n###\n\nattachLFO = (node, attribute, {frequency, amplitude}) ->\n {context} = node\n\n lfo = context.createOscillator()\n lfo.frequency.value = frequency\n lfo.start()\n\n gain = context.createGain()\n gain.gain.value = amplitude\n\n gain.connect node[attribute]\n\n###\ninput -> delay -> gain -> destination\n -> destination\n###\n# Simple slapback delay\nslapback = (destination) ->\n {context} = destination\n\n d1 = context.createDelay 0.1\n d1.delayTime.value = 0.035\n\n g1 = context.createGain()\n g1.gain.value = 0.42\n\n d1.connect g1\n g1.connect destination\n\n # Input\n input = context.createGain()\n input.gain.value = 0.75\n input.connect destination\n input.connect d1\n\n return input\n\n### -> feedback \ninput -> d1 -> g1 -> f1 -> destination\n -> d2 /\n###\ncygnus = (destination) ->\n {context} = destination\n\n d1 = context.createDelay 0.1\n d1.delayTime.value = 0.069\n \n d2 = context.createDelay 0.5\n d2.delayTime.value = 0.171\n\n attachLFO d2, \"delayTime\",\n frequency: 1.3\n amplitude: 0.1\n\n g1 = context.createGain()\n g1.gain.value = 0.3\n\n feedback = context.createGain()\n feedback.gain.value = 0.75\n\n d1.connect g1\n d2.connect g1\n\n feedback.connect d1\n feedback.connect d2\n\n f1 = context.createBiquadFilter()\n f1.type = \"bandpass\"\n f1.frequency.value = 500\n f1.Q.value = 0.5\n\n g1.connect f1\n\n attachLFO f1, \"frequency\",\n frequency: 0.44\n amplitude: 420\n\n attachLFO f1, \"Q\",\n frequency: 0.77\n amplitude: 0.25\n\n # Input\n input = context.createGain()\n input.connect f1\n input.connect d1\n input.connect d2\n\n f1.connect feedback\n f1.connect destination\n\n return input\n\n###\ninput -> input\n -> wet -> delay1 -> filter -> feedback -> destination\n -> delay2 -> feedback -> destination\n |\n lfo\n\n -> dry -> destination\n###\n# Chorus\nchorus = (destination) ->\n {context} = destination\n\n # Filtered delay\n d1 = context.createDelay(0.1)\n d1.delayTime.value = 0.005\n\n f1 = context.createBiquadFilter()\n f1.type = \"highpass\"\n f1.frequency.value = 1000\n\n d1.connect f1\n\n # Modulated delay\n d2 = context.createDelay(0.1)\n d2.delayTime.value = 0.007\n\n attachLFO d2, \"delayTime\",\n frequency: 2.64\n amplitude: 0.00308\n\n wetGain = context.createGain()\n wetGain.gain.value = 0.5\n\n wetGain.connect d1\n wetGain.connect d2\n\n dryGain = context.createGain()\n dryGain.gain.value = 0.5\n\n # Input\n input = context.createGain()\n input.connect dryGain\n input.connect wetGain\n dryGain.connect destination\n\n feedback = context.createGain()\n feedback.gain.value = 0.15\n feedback.connect input\n\n f1.connect destination\n f1.connect feedback\n d2.connect destination\n d2.connect feedback\n\n return input\n\n###\ninput -> wet -> delay -> filter -> destination\n | \\-> feedback -> input\n lfo\n -> dry -> destination\n###\n# Flanger (Experimental)\nflanger = (destination) ->\n {context} = destination\n\n # Filtered delay\n d1 = context.createDelay(0.01)\n d1.delayTime.value = 0.00073\n\n f1 = context.createBiquadFilter()\n f1.type = \"lowpass\"\n f1.frequency.value = 1000\n f1.Q.value = 0.5\n\n d1.connect f1\n\n attachLFO d1, \"delayTime\",\n frequency: 0.11\n amplitude: 0.00045\n\n wetGain = context.createGain()\n wetGain.gain.value = 1\n wetGain.connect d1\n\n dryGain = context.createGain()\n dryGain.gain.value = 0\n\n # Input\n input = context.createGain()\n input.connect dryGain\n dryGain.connect destination\n input.connect wetGain\n\n feedback = context.createGain()\n feedback.gain.value = 0.75\n feedback.connect input\n\n f1.connect feedback\n f1.connect destination\n\n return input\n\n# Experimental\nphaser = (destination) ->\n {context} = destination\n\n input = context.createGain()\n input.gain.value = 0.75\n\n lfo = context.createOscillator()\n lfo.frequency.value = 0.5\n lfo.start()\n\n attachLFO lfo, \"frequency\",\n amplitude: 0.25\n frequency: 0.25\n\n lfoGain = context.createGain()\n lfoGain.gain.value = 110\n lfo.connect lfoGain\n\n i = 0\n prev = input\n while i < 8\n i++\n\n f = context.createBiquadFilter()\n f.type = \"allpass\"\n f.frequency.value = i * 220\n f.Q.value = 16\n \n lfoGain.connect f.frequency\n\n if prev\n prev.connect f\n prev = f\n\n # Feedback delay\n d1 = context.createDelay(0.01)\n d1.delayTime.value = 0.0073\n\n feedback = context.createGain()\n feedback.gain.value = 0.45\n feedback.connect d1\n\n d1.connect input\n\n prev.connect feedback\n prev.connect destination\n\n return input\n\ncompressor = (destination) ->\n {context} = destination\n\n compressor = context.createDynamicsCompressor()\n compressor.attack.value = 0.0035\n compressor.connect destination\n\n wetGain = context.createGain()\n wetGain.gain.value = 0.5\n wetGain.connect compressor\n\n dryGain = context.createGain()\n dryGain.gain.value = 0.5\n\n # Input\n input = context.createGain()\n input.connect dryGain\n dryGain.connect destination\n input.connect wetGain\n\n return input\n\nlimiter = (destination) ->\n {context} = destination\n limiter = context.createDynamicsCompressor()\n\n limiter.threshold.value = 0.0 # leave some headroom\n limiter.knee.value = 0.0 # Hard Knee\n limiter.ratio.value = 20.0 # Max ratio\n limiter.attack.value = 0.005\n limiter.release.value = 0.01\n\n limiter.connect destination\n\n return limiter\n\nsweepingPan = (destination) ->\n {context} = destination\n panner = context.createStereoPanner()\n\n lfo = context.createOscillator()\n lfo.frequency.value = 0.25\n lfo.type = \"sine\"\n lfo.start()\n\n attachLFO lfo, \"frequency\",\n frequency: 0.77\n amplitude: 0.125\n\n lfo.connect panner.pan\n\n panner.connect destination\n\n return panner\n\n###\nStereo Ping Pong Delay\n\nFrom my brief research there seem to be a few different ways people wire up\nthese kind of stereo ping pong delays. My goal is to have it be relatively\nsimple, have the unmodified signal proceed through each channel, and have the\ndelayed signal on the opposite channel.\n\n -> \nL -> DelayL -> GainL FeedbackL\n X\nR -> DelayR -> GainR FeedbackR\n -> \n\nThis diagram is pretty weak ;_;\n###\npingPongDelay = (destination) ->\n {context} = destination\n\n splitter = context.createChannelSplitter(2)\n merger = context.createChannelMerger(2)\n\n delayL = context.createDelay()\n delayL.delayTime.value = 0.045\n delayR = context.createDelay()\n delayR.delayTime.value = 0.024\n\n splitter.connect delayL, 0\n splitter.connect delayR, 1\n\n gainL = context.createGain()\n gainL.gain.value = 0.4\n gainR = context.createGain()\n gainR.gain.value = 0.4\n\n delayL.connect gainL\n delayR.connect gainR\n\n # Right swaps to left\n gainR.connect merger, 0, 0\n gainL.connect merger, 0, 1\n\n # Feedback crosses over\n gainL.connect delayR\n gainR.connect delayL\n\n # Raw signal goes straight through\n splitter.connect merger, 0, 0\n splitter.connect merger, 1, 1\n\n output = context.createGain()\n output.gain.value = 0.75\n output.connect destination\n\n merger.connect output\n\n return splitter\n\n# Stereo analyser\nstereoAnalyser = (destination) ->\n {context} = destination\n\n splitter = context.createChannelSplitter(2)\n merger = context.createChannelMerger(2)\n\n analyserL = context.createAnalyser()\n analyserR = context.createAnalyser()\n analyserL.smoothingTimeConstant.value = 0\n analyserR.smoothingTimeConstant.value = 0\n\n splitter.connect analyserL, 0\n splitter.connect analyserR, 1\n\n analyserL.connect merger, 0, 0\n analyserR.connect merger, 0, 1\n\n merger.connect destination\n\n Object.assign splitter,\n l: analyserL\n r: analyserR\n\n return splitter\n\n# 50/50 panner to ensure mono signal is stereo before going into ping pong delay\n# etc.\ndefaultPanner = (destination) ->\n {context} = destination\n\n panner = context.createStereoPanner()\n panner.connect destination\n\n return panner\n\nwater = (destination) ->\n {context} = destination\n\n f1 = context.createBiquadFilter()\n f1.type = \"bandpass\"\n f1.frequency.value = 250\n f1.Q.value = 0.25\n\n f2 = context.createBiquadFilter()\n f2.type = \"peaking\"\n f2.frequency.value = 440 / 8\n f2.Q.value = 1\n f2.gain.value = 3\n\n f3 = context.createBiquadFilter()\n f3.type = \"lowpass\"\n f3.frequency.value = 197\n f3.Q.value = 1\n\n f1.connect f2\n f2.connect destination\n f3.connect destination\n\n g1 = context.createGain()\n g1.gain.value = 0.7\n g1.connect f3\n\n d1 = context.createDelay()\n d1.delayTime.value = 0.21\n d1.connect g1\n\n input = context.createGain()\n input.connect d1\n input.connect f1\n\n chorus input\n\nmodule.exports =\n choices: [\n \"purple\"\n \"cygnus\"\n \"psy\"\n \"water\"\n ]\n chorus: chorus\n compressor: compressor\n cygnus: cygnus\n defaultPanner: defaultPanner\n flanger: flanger\n limiter: limiter\n pingPongDelay: pingPongDelay\n # Simple slapback delay\n purple: slapback\n fire: (destination) ->\n flanger destination\n psy: (destination) ->\n phaser destination\n slapback: slapback\n stereoAnalyser: stereoAnalyser\n sweepingPan: sweepingPan\n water: water\n"
},
"lib/test/fx.coffee": {
"content": ""
},
"views/stereo-analyser.coffee": {
"content": "###\nStereo analyser node view\n\nStatus: WIP\n###\n\n{stereoAnalyser} = FX = require \"../lib/fx\"\n\nTemplate = require \"../templates/stereo-analyser\"\n\nmodule.exports = ({destination}) ->\n analyser = stereoAnalyser(destination)\n\n leftCanvas = drawAnalyserCanvas(analyser.l)\n rightCanvas = drawAnalyserCanvas(analyser.r)\n\n self =\n leftCanvas: leftCanvas\n rightCanvas: rightCanvas\n analyserNode: analyser\n element: null\n\n self.element = Template self\n\n return self\n\ndrawAnalyserCanvas = (analyser) ->\n canvas = document.createElement 'canvas'\n canvas.width = 200\n canvas.height = 34\n canvasCtx = canvas.getContext('2d')\n canvasCtx.fillStyle = \"rgba(103, 58, 183 ,0.19)\"\n canvasCtx.strokeStyle = \"#673ab7\"\n\n bufferLength = analyser.frequencyBinCount\n dataArray = new Uint8Array(bufferLength)\n\n draw = ->\n requestAnimationFrame(draw)\n\n {width, height} = canvas\n\n analyser.getByteTimeDomainData(dataArray)\n\n canvasCtx.clearRect(0, 0, width, height)\n canvasCtx.fillRect(0, 0, width, height)\n\n canvasCtx.lineWidth = 2\n canvasCtx.beginPath()\n\n sliceWidth = canvas.width * 1.0 / bufferLength\n x = 0\n \n i = 0\n while i < bufferLength\n v = dataArray[i] / 128.0\n y = v * canvas.height / 2\n\n if i is 0\n canvasCtx.moveTo(x, y)\n else\n canvasCtx.lineTo(x, y)\n\n x += sliceWidth\n i++\n\n canvasCtx.lineTo(width, height / 2)\n canvasCtx.stroke()\n\n draw()\n\n return canvas\n"
},
"data/views/stereo-analyser.coffee": {
"content": "{destination} = new AudioContext\n\nmodule.exports =\n destination: destination\n"
},
"templates/stereo-analyser.jadelet": {
"content": "aside.stereo-analyser\n label L\n @leftCanvas\n label R\n @rightCanvas\n"
},
"data/templates/stereo-analyser.coffee": {
"content": ""
},
"views/fx-picker.coffee": {
"content": "###\nStereo analyser node view\n\nStatus: WIP\n###\n\n{chorus, delay} = FX = require \"../lib/fx\"\n\nTemplate = require \"../templates/fx-picker\"\n\n{Jadelet} = system.ui\n\nItemTemplate = Jadelet.exec \"\"\"\n button(@click @style @title class=@active)\n\"\"\"\n\nstyleFor = (name) ->\n if name is \"purple\"\n backgroundColor: \"#ede7f6\"\n else\n backgroundImage: \"url(https://danielx.net/composer/fx/#{name}.jpg)\"\n\nmodule.exports = FXPicker = ({presetName}) ->\n self =\n choices: ->\n FX.choices\n items: ->\n @choices().map (name) ->\n ItemTemplate\n title: name\n click: ->\n presetName(name)\n style: ->\n styleFor(name)\n active: ->\n \"active\" if presetName() is name\n\n element: null\n\n self.element = Template self\n\n return self\n\nFXPicker.styleFor = styleFor\n"
},
"data/views/fx-picker.coffee": {
"content": "{Observable} = system.ui\n\nmodule.exports =\n presetName: Observable \"cygnus\"\n"
},
"templates/fx-picker.jadelet": {
"content": "aside.fx-picker\n label FX\n @items\n"
},
"data/templates/fx-picker.coffee": {
"content": ""
},
"data/demo-songs.coffee": {
"content": "module.exports = [{\n title: \"Dubsgrace\"\n author: \"diamondblaze413\"\n slug: \"api-4IuXACKoqtSke9jTAThC5I_eWc4me53ze-1cri-L8kE\"\n}, {\n title: \"Orinoco Flow\"\n author: \"Enya\"\n slug: \"api-xeSsrUhuXkiWKC2_4pwoaCc80rK1hRX7uLeRM8dOs7g\"\n}, {\n title: \"MeGaLoVania\"\n author: \"Toby Fox (arranged by Jackattack413)\"\n slug: \"50c687fa90400971abb010e741aa78c4\"\n}, {\n title: \"Mushroom Forest\"\n author: \"Junko Tamiya (Little Nemo: The Dream Master NES) (arranged by Daniel X Moore)\"\n slug: \"api-KyDf0uvvBi4xOv-_Ze8-cwrEqHC_Qy_fNqgg984FbN8\"\n}, {\n title: \"Wind Forest\"\n author: \"Joe Hisaishi (arranged by A. E. Moore)\"\n slug: \"api-4G6CcZfyxeWyEjtnBNq_3_D3rWC1AYVYpqoxBpbit08\"\n}, {\n title: \"600 AD\"\n author: \"(arranged by LachrymatoryAgent)\"\n slug: \"13ff6ed6dd7c14fdaf63\"\n}]\n"
},
"views/demo-picker.coffee": {
"content": "demoSongs = require \"../data/demo-songs\"\n\n{Jadelet, Modal} = system.ui\n\nTemplate = Jadelet.exec \"\"\"\nsection.demo-picker\n h2 🎵 Demo Songs 🎵\n button.close(click=@close) X\n table\n thead\n tr\n th Title\n th Author\n tbody\n @items\n\"\"\"\n\nRowTemplate = Jadelet.exec \"\"\"\ntr(@click)\n td @title\n td @author\n\"\"\"\n\nmodule.exports = (player) ->\n player.showDemoSongPicker ?= ->\n Modal.show self.element\n\n self =\n close: -> Modal.hide()\n items: ->\n demoSongs.map ({title, author, slug}) ->\n RowTemplate\n title: title\n author: author\n click: ->\n Modal.hide()\n\n player.loadFromSlug(slug)\n\n element: null\n\n self.element = Template self\n\n return self\n"
},
"data/views/demo-picker.coffee": {
"content": "module.exports =\n loadFromSlug: console.log\n"
},
"views/meter-picker.coffee": {
"content": "{Jadelet} = system.ui\n\nTemplate = Jadelet.exec \"\"\"\n aside.meter-picker\n @buttons\n\"\"\"\n\nButtonTemplate = Jadelet.exec \"\"\"\n button(@click class=@active) @meter\n\"\"\"\n\nmodule.exports = (player) ->\n\n choices = [\n \"4/4\"\n \"3/4\"\n \"5/4\"\n \"7/4\"\n ]\n\n self =\n buttons: ->\n choices.map (meter, i) ->\n ButtonTemplate\n active: ->\n \"active\" if meter is player.timeSignature()\n click: ->\n player.timeSignature meter\n \n meter: meter\n\n element: null\n\n self.element = Template self\n\n return self\n"
},
"const.coffee": {
"content": "module.exports =\n CLEF_OFFSET: 256\n PIXELS_PER_BEAT: 96\n"
},
"data/views/meter-picker.coffee": {
"content": ""
},
"lib/meter-background.coffee": {
"content": "###\nGenerates background images that can be used as for the meter in different time\nsignatures.\n\nExports a promise that is fulfilled with an lookup table of background image\nurls indexd by time signatures: \"4/4\", \"3/4\", etc.\n\n###\n\n\n{\n PIXELS_PER_BEAT\n} = require \"../const\"\n\ncanvas = document.createElement 'canvas'\ncontext = canvas.getContext('2d')\n\npxs = PIXELS_PER_BEAT\n\nnumerators = [2..7]\n\nbgUrls = Promise.all numerators.map (n) ->\n canvas.height = 1\n canvas.width = n * pxs\n\n context.fillStyle = \"rgba(0, 0, 0, 0.5)\"\n i = 0\n while i < n\n i++\n context.fillRect(i * pxs, 0, 1, 1)\n context.fillStyle = \"black\"\n context.fillRect(i * pxs - 2, 0, 2, 1)\n\n urlPromise = new Promise (resolve, reject) ->\n canvas.toBlob(resolve)\n .then (blob) ->\n # Preload and return\n (new Image).src = URL.createObjectURL(blob)\n.then (urls) ->\n table = {}\n numerators.forEach (n, i) ->\n table[n] = urls[i]\n\n return table\n\nmodule.exports = bgUrls\n"
},
"lib/test/meter-background.coffee": {
"content": ""
},
"workspaces/bench.coffee": {
"content": "###\nA test bench for experimenting with the web audio api\n\nGoal: Create nodes and connect them visually to create sweet effects!\nSave and load effects\nRe-use complex effects as simpler components.\n###\n{Observable} = system.ui\nFX = require \"../lib/fx\"\n\nButton = (params={}) ->\n button = document.createElement 'button'\n Object.assign button, params\n\n# Map nodes to a unique id per context so we can connect them through the UI\nnextId = (context) ->\n lastId = context.lastId ? 0\n lastId++\n\n context.lastId = lastId\n \n return lastId\n\nOsc = (context, params={}) ->\n id = nextId(context)\n n = context.createOscillator()\n\n Object.keys(params).forEach (param) ->\n n[param].value = params[param]\n\n return n\n\nGain = (context, value) ->\n gain = context.createGain()\n gain.gain.value = value\n return gain\n\nControl = require \"../views/slider\"\n\n# Experiment 1\n# Can you use a negative start time to change the phase of a webaudio oscillator?\n\n# Cannot use a negative time, throws an error.\n# Can use an offset equal to half the frequency\n# if the frequency doesn't divide the sample rate precisely\n# Can start at a time after zero\n# Using a gain to set the value at a time works, eliminates the pop\n# no audible artifacts!\n# Conclusion: Phase matching works as long as it is in the future of the audio\n# context's currentTime\nexp1 = ->\n global.context = context = new AudioContext\n sampleRate: 44100\n\n f = 440\n t = 0.0625\n\n g = Gain context, 0\n osc1 = Osc context,\n frequency: f\n osc2 = Osc context,\n frequency: f\n\n osc1.connect g\n g.connect context.destination\n osc2.connect context.destination\n\n # TODO: Consolidate this bind, add min/max, and validating\n o = Observable osc1.frequency.value\n o.observe (v) ->\n osc1.frequency.value = v\n\n c = Control\n value: o\n\n # Does it keep the phase after a timeout?\n # No, the phase gets fucked, unless the time is >= currentTime\n setTimeout ->\n t = context.currentTime\n # 1/2 phase offset\n t2 = t + 1 / (2 * f)\n osc1.start t\n osc2.start t2\n\n g.gain.setValueAtTime(1, t2)\n , 100\n\n document.body.appendChild c.element\n\n# Delay Knob\n# Experimenting with creating an adjustable delay knob\nexp2 = ->\n global.context = context = new AudioContext\n sampleRate: 44100\n\n delay = context.createDelay(1)\n delay.delay.value = 0.070\n\n g = Gain context,\n gain: 0.4\n\n delay.connect g\n g.connect context.destination\n\n # TODO: input signal?\n # input.connect delay\n # input.connect destination\n\n# Connect a volume knob on an input to a reverb effect\nexp3 = ->\n \n\ndocument.body.appendChild Button\n textContent: \"Exp 1\"\n onclick: exp1\n\ndocument.body.appendChild Button\n textContent: \"Stop\"\n onclick: ->\n context.close()\n\nDialTemplate = require \"../templates/dial\"\n\ndocument.body.appendChild DialTemplate()\n"
},
"data/views/bench.coffee": {
"content": ""
},
"templates/dial.jadelet": {
"content": "svg(xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 800 600\")\r\n circle(cx=\"400\" cy=\"300\" r=\"250\" stroke-width=\"20\" stroke=\"#f00\" fill=\"#ff0\")\r\n"
},
"templates/slider.jadelet": {
"content": "label.slider\n @name\n | \n input(type=\"number\" @value @min @max @step)\n | \n @unit\n"
},
"views/slider.coffee": {
"content": "###\nA simple slider control.\n###\n\nSliderTemplate = require \"../templates/slider\"\n\nmodule.exports = (param) ->\n self = Object.assign {}, param,\n element: null\n\n self.element = element = SliderTemplate self\n\n element.addEventListener\n\n return self\n"
},
"data/views/slider.coffee": {
"content": "{Observable} = system.ui\n\nmodule.exports =\n name: \"Delay\"\n value: Observable 100\n min: 0\n max: 250\n unit: \"ms\"\n"
},
"data/templates/slider.coffee": {
"content": ""
},
"templates/persistence.jadelet": {
"content": "section.persistence\n button(click=@showDemoSongPicker) Load a demo song\n button(click=@saveFile) Save to disk\n button(click=@openFile) Load from disk\n button(click=@exportAudio) Export to .wav or .mp3\n"
},
"templates/arranger.jadelet": {
"content": "section.arranger\n h2 Sections\n table\n thead\n tr\n th Name\n th Time Signature\n th Key Signature\n th Length\n th Tempo\n th FX\n th Action\n tbody\n @items\n actions\n button(click=@newSection) + New Section\n"
},
"templates/arranger-row.jadelet": {
"content": "tr\n td\n input(value=@name)\n td\n select(value=@timeSignature)\n option(value=\"4/4\") 4/4\n option(value=\"3/4\") 3/4\n option(value=\"5/4\") 5/4\n option(value=\"7/4\") 7/4\n td\n select(value=@keySignature)\n option(value=-7) C♭\n option(value=-6) G♭ / e♭\n option(value=-5) D♭ / b♭\n option(value=-4) A♭ / f\n option(value=-3) E / c\n option(value=-2) B / g\n option(value=-1) F / d\n option(value=0) C / a\n option(value=1) G / e\n option(value=2) D / b\n option(value=3) A / f♯\n option(value=4) E / c♯\n option(value=5) B / g♯\n option(value=6) F♯ / d♯\n option(value=7) C♯\n td\n input(value=@length type=\"number\" max=9999 min=1 step=1)\n td\n input(value=@tempo type=\"number\" max=900 min=1 step=1)\n td\n @fxPickerElement\n td\n button(title=\"Move Up\" click=@moveUp disabled=@moveUpDisabled) ↑\n button(title=\"Move Down\" click=@moveDown disabled=@moveDownDisabled) ↓\n button(title=\"Duplicate\" click=@duplicate) +\n button(title=\"Remove\" click=@remove disabled=@removeDisabled) X\n"
},
"views/arranger.coffee": {
"content": "{Jadelet, Modal} = system.ui\n\nSection = require \"../lib/section\"\n\nTemplate = require \"../templates/arranger\"\nRowTemplate = require \"../templates/arranger-row\"\n\nFXPicker = require \"./fx-picker\"\n\ndup = (section) ->\n Section JSON.parse JSON.stringify section.toJSON()\n\nSectionPresenter = (section, sections, index) ->\n fxPicker = FXPicker\n presetName: section.presetName\n\n Object.assign {}, section,\n duplicate: ->\n sections.splice index+1, 0, dup section\n moveUp: ->\n sections.remove(section)\n sections.splice index-1, 0, section\n moveDown: ->\n sections.remove(section)\n sections.splice index+1, 0, section\n moveUpDisabled: ->\n index is 0\n moveDownDisabled: ->\n index is sections.length - 1\n remove: ->\n sections.remove section\n removeDisabled: ->\n sections.length <= 1\n fxPickerElement: fxPicker.element\n\nmodule.exports = (player) ->\n player.showArranger ?= ->\n Modal.show self.element\n\n self =\n items: ->\n player.sections().map (section, i) ->\n RowTemplate SectionPresenter section, player.sections, i\n\n newSection: ->\n player.sections.push Section()\n\n element: null\n\n self.element = Template self\n\n return self\n"
},
"data/views/arranger.coffee": {
"content": "{Observable} = system.ui\n\nSection = require \"/lib/section\"\n\nmodule.exports =\n sections: Observable [\n Section()\n ]\n"
},
"views/actions.coffee": {
"content": "Presenter = (player) ->\n Object.assign {}, player,\n length: ->\n player.activeSection().length.apply(null, arguments)\n\n tempo: ->\n player.activeSection().tempo.apply(null, arguments)\n\nmodule.exports = (player) ->\n element: require(\"../templates/actions\") Presenter player\n"
},
"test/main.coffee": {
"content": "Song = require \"../song-v2\"\n\nPlayer = require \"../player\"\n\ndescribe \"Song V2\", ->\n it \"should add notes to the proper sections\", ->\n song = Song\n sections: [{\n length: 4\n }, {\n length: 4\n }]\n\n assert.equal song.length(), 8\n\n # Notes are added to the section they belong to\n song.addNote [0, 0, 0, 0]\n assert.equal song.sections()[0].notes().length, 1\n assert.equal song.sections()[1].notes().length, 0\n\n song.addNote [4, 0, 0, 0]\n assert.equal song.sections()[0].notes().length, 1\n assert.equal song.sections()[1].notes().length, 1\n\n # Adding notes outside of range does nothing\n song.addNote [8, 0, 0, 0]\n assert.equal song.sections()[0].notes().length, 1\n assert.equal song.sections()[1].notes().length, 1\n\n called = false\n song.upcomingNotes 0, 4, (note) ->\n called = true\n assert called\n\n called = false\n song.upcomingNotes 4, 4, (note) ->\n called = true\n assert called\n\n called = false\n song.upcomingNotes 8, 4, (note) ->\n called = true\n assert !called\n\n it \"should return the proper time offsets in multi-section multi-tempo settings\", ->\n song = Song\n sections: [{\n length: 1\n tempo: 120\n notes: [\n [0, 0, 0, 0]\n [0.25, 0, 0, 0]\n [0.5, 0, 0, 0]\n [0.75, 0, 0, 0]\n ]\n }, {\n length: 1\n tempo: 120\n notes: [\n [0, 0, 0, 0]\n [0.25, 0, 0, 0]\n [0.5, 0, 0, 0]\n [0.75, 0, 0, 0]\n ]\n }]\n\n expectations = [\n 0\n 0.125\n 0.25\n 0.375\n 0.5\n 0.625\n 0.75\n 0.875\n ]\n\n i = 0\n song.upcomingNotes 0, 2, (note, section, s) ->\n assert.equal expectations[i], s\n i++\n\n assert.equal i, 8\n\n\n expectations = [\n 0.5\n 0.625\n 0.75\n 0.875\n ]\n\n i = 0\n song.upcomingNotes 1, 2, (note, section, s) ->\n assert.equal expectations[i], s\n i++\n , 0\n\n assert.equal i, 4\n\n expectations = [\n 0\n 0.125\n 0.25\n 0.375\n ]\n\n i = 0\n song.upcomingNotes 1, 2, (note, section, s) ->\n assert.equal expectations[i], s\n i++\n\ndescribe \"wrapping\", ->\n it \"should have proper tempo when wrapping\", ->\n song = Song\n sections: [{\n length: 4\n tempo: 60\n notes: [\n [0, 0, 0, 0]\n ]\n }, {\n length: 4\n tempo: 240\n }]\n\n song.upcomingNotes 0, 1, (note, section, s) ->\n assert.equal s, 0.25\n , -1\n\ndescribe \"resection\", ->\n it \"should resection notes\", ->\n player = Player\n song:\n sections: [{\n length: 4\n notes: [\n [0, 0, 0, 0]\n [1, 0, 0, 0]\n [2, 0, 0, 0]\n [3, 0, 0, 0]\n ]\n }, {\n length: 4\n }]\n\n notes = player.song().sections()[0].notes().slice()\n\n player.moveNotes(notes, 1, 0)\n assert.deepEqual(player.song().sections()[0].notes(), [\n [1, 0, 0, 0]\n [2, 0, 0, 0]\n [3, 0, 0, 0]\n ])\n assert.deepEqual(player.song().sections()[1].notes(), [\n [0, 0, 0, 0]\n ])\n\n player.moveNotes(notes, 3, 0)\n assert.deepEqual(player.song().sections()[0].notes(), [\n ])\n assert.deepEqual(player.song().sections()[1].notes(), [\n [3, 0, 0, 0]\n [0, 0, 0, 0]\n [1, 0, 0, 0]\n [2, 0, 0, 0]\n ])\n\n player.moveNotes(notes, -2, 0)\n assert.deepEqual(player.song().sections()[0].notes(), [\n [2, 0, 0, 0]\n [3, 0, 0, 0]\n ])\n assert.deepEqual(player.song().sections()[1].notes(), [\n [1, 0, 0, 0]\n [0, 0, 0, 0]\n ])"
},
"scratch.coffee": {
"content": " # Schedules upcoming sounds to play\n playUpcomingSounds = (current, fromBeat, toBeat) ->\n dt = toBeat - fromBeat\n return if dt <=0\n\n self.upcomingNotes fromBeat, dt, ([beat, staffNote, accidental, instrument], section, s) ->\n pitch = staffNoteToPitch(staffNote, accidental)\n\n self.playNote instrument, pitch, s\n\n # playTime in beats\n # elapsedSeconds in seconds\n computeElapsedBeats = (playTime, elapsedSeconds) ->\n if playTime is self.length()\n playTime = 0\n \n [section, beatsLeft] = self.sectionAt(playTime)\n _bps = section.tempo() / 60\n\n beats = elapsedSeconds * _bps\n\n if beats > beatsLeft\n beatsLeft + computeElapsedBeats playTime + beatsLeft, elapsedSeconds - beatsLeft / _bps\n else\n beats\n\n # t, dt in beats\n computeElapsedSeconds = (t, dt) ->\n [section, beatsLeft] = self.sectionAt(playTime)"
},
"lib/test/song.coffee": {
"content": "Song = require \"/song-v2\"\n\ndescribe \"Song\", ->\n it \"should know its duration in seconds\", ->\n song = Song\n sections: [{\n length: 90\n tempo: 60\n }, {\n length: 180\n tempo: 120\n }]\n\n assert.equal song.duration(), 180\n"
},
"data/views/actions.coffee": {
"content": ""
},
"templates/section.jadelet": {
"content": "song-section(class=@meterClass)\n div.key-signature(class=@c1 class=@c2)\n div\n div\n div\n div\n div\n div\n div\n span.measure-number 1\n span.measure-number 5\n"
},
"data/templates/section.coffee": {
"content": ""
},
"data/templates/arranger-row.coffee": {
"content": "module.exports =\n keySignature: 0\n timeSignature: \"4/4\"\n"
},
"data/templates/arranger.coffee": {
"content": "module.exports =\n items: Observable [\n {}\n {}\n ]\n newSection: ->\n @items.push {}\n"
},
"lib/midi-file.coffee": {
"content": "###\nRead MIDI data from an array buffer\n\nStatus: Copied from audio-builder actual dev should take place there.\n###\n\n# was read in stream.js\nreadAscii = (state, length) ->\n {data, position} = state\n state.position = position + length\n\n return String.fromCharCode.apply(String, data.slice(position, position + length))\n\nsubarray = (state, length) ->\n {data, position} = state\n state.position = position + length\n \n return data.subarray(position, position + length)\n\n# read a big-endian 32-bit integer\nreadInt32 = (state) ->\n {data, position} = state\n state.position = position + 4\n\n return (\n (data[position] << 24) +\n (data[position + 1] << 16) +\n (data[position + 2] << 8) +\n data[position + 3])\n\nreadInt24 = (state) ->\n {data, position} = state\n state.position = position + 3\n\n return (\n (data[position] << 16) +\n (data[position + 1]<< 8) +\n data[position + 2])\n\n# read a big-endian 16-bit integer\nreadInt16 = (state) ->\n {data, position} = state\n state.position = position + 2\n\n return (\n (data[position] << 8) +\n data[position + 1])\n\n# read an 8-bit integer\nreadInt8 = (state, signed) ->\n {data, position} = state\n state.position = position + 1\n\n result = data[position]\n if signed and result > 127\n result -= 256\n\n return result\n\n###\nread a MIDI-style variable-length integer\n(big-endian value in groups of 7 bits,\nwith top bit set to signify that another byte follows)\n###\nreadVarInt = (state) ->\n result = 0\n loop\n b = readInt8(state)\n if b & 0x80\n result += (b & 0x7f)\n result <<= 7\n else\n return result + b\n\nreadChunk = (state) ->\n id = readAscii(state, 4)\n length = readInt32(state)\n sub = subarray(state, length)\n\n return {\n id: id\n length: length\n data: sub\n position: 0\n }\n\n# Note: Maybe don't convert bytes to strings and just have a printer fn?\n# Parse a raw event\nparseEvent = ({deltaTime, data, type:lastEventType}) ->\n event = {deltaTime}\n state =\n data: data\n position: 0\n\n type = readInt8(state)\n\n if (type & 0xf0) is 0xf0\n if type is 0xff\n event.type = \"meta\"\n subtype = readInt8(state)\n length = readVarInt(state)\n switch subtype\n when 0x00\n event.subtype = 'sequenceNumber'\n if (length != 2) \n throw 'Expected length for sequenceNumber event is 2, got ' + length\n event.number = readInt16(state)\n when 0x01\n event.subtype = 'text'\n event.text = readAscii(state, length)\n when 0x02\n event.subtype = 'copyrightNotice'\n event.text = readAscii(state, length)\n when 0x03\n event.subtype = 'trackName'\n event.text = readAscii(state, length)\n when 0x04\n event.subtype = 'instrumentName'\n event.text = readAscii(state, length)\n when 0x05\n event.subtype = 'lyrics'\n event.text = readAscii(state, length)\n when 0x06\n event.subtype = 'marker'\n event.text = readAscii(state, length)\n when 0x07\n event.subtype = 'cuePoint'\n event.text = readAscii(state, length)\n when 0x20\n event.subtype = 'midiChannelPrefix'\n if (length != 1) \n throw 'Expected length for midiChannelPrefix event is 1, got ' + length\n event.channel = readInt8(state)\n when 0x2f\n event.subtype = 'endOfTrack'\n if length\n throw 'Expected length for endOfTrack event is 0, got ' + length\n when 0x51\n event.subtype = 'setTempo'\n if length != 3\n throw 'Expected length for setTempo event is 3, got ' + length\n event.microsecondsPerBeat = readInt24(state)\n when 0x54\n event.subtype = 'smpteOffset'\n if length != 5\n throw 'Expected length for smpteOffset event is 5, got ' + length\n hourByte = readInt8(state)\n event.frameRate = {\n 0x00: 24, 0x20: 25, 0x40: 29, 0x60: 30\n }[hourByte & 0x60]\n event.hour = hourByte & 0x1f\n event.min = readInt8(state)\n event.sec = readInt8(state)\n event.frame = readInt8(state)\n event.subframe = readInt8(state)\n when 0x58\n event.subtype = 'timeSignature'\n if (length != 4) \n throw 'Expected length for timeSignature event is 4, got ' + length\n event.numerator = readInt8(state)\n event.denominator = Math.pow(2, readInt8(state))\n event.metronome = readInt8(state)\n event.thirtyseconds = readInt8(state)\n when 0x59\n event.subtype = 'keySignature'\n if length != 2\n throw 'Expected length for keySignature event is 2, got ' + length\n event.key = readInt8(state, true)\n event.scale = readInt8(state)\n when 0x7f\n event.subtype = 'sequencerSpecific'\n event.data = subarray(state, length)\n else\n # console.log(\"Unrecognised meta event subtype: \" + subtypeByte)\n event.subtype = 'unknown'\n event.data = subarray(state, length)\n else if (type == 0xf0)\n event.type = 'sysEx'\n length = readVarInt(state)\n event.data = subarray(state, length)\n else if (type == 0xf7)\n event.type = 'dividedSysEx'\n length = readVarInt(state)\n event.data = subarray(state, length)\n else\n throw 'Unrecognised MIDI event type byte: ' + type\n else\n # channel event\n if (type & 0x80) is 0\n # running status - reuse lastEventType as the event type.\n #\ttype is actually the first parameter\n param1 = type\n type = lastEventType\n else\n param1 = readInt8(state)\n\n # NOTE: Event type has been shifted down\n eventType = type >> 4\n event.channel = type & 0x0f\n event.type = 'channel'\n switch eventType\n when 0x08\n event.subtype = 'noteOff'\n event.noteNumber = param1\n event.velocity = readInt8(state)\n when 0x09\n if event.velocity == 0\n event.subtype = 'noteOff'\n else\n event.subtype = 'noteOn'\n event.noteNumber = param1\n event.velocity = readInt8(state)\n when 0x0a\n event.subtype = 'noteAftertouch'\n event.noteNumber = param1\n event.amount = readInt8(state)\n when 0x0b\n event.subtype = 'controller'\n event.controllerType = param1\n event.value = readInt8(state)\n when 0x0c\n event.subtype = 'programChange'\n event.programNumber = param1\n when 0x0d\n event.subtype = 'channelAftertouch'\n event.amount = param1\n when 0x0e\n event.subtype = 'pitchBend'\n param2 = readInt8(state)\n event.value = param1 + (param2 << 7)\n else\n throw 'Unrecognised MIDI event type: ' + eventType\n\n return event\n\n# Read an event returning the deltaTime, the event type, and the raw event data\n# advancing the state position to the next event. The event type may depend on\n# the previous state for the case of running status messages.\nreadEvent = (state) ->\n {position, data} = state\n # Consume the deltaTime\n deltaTime = readVarInt(state)\n \n # Find the start position of the data\n start = state.position\n type = readInt8(state)\n\n switch type\n when 0xff # meta\n metaType = readInt8(state)\n length = readVarInt(state)\n\n offset = state.position - start\n state.position = start\n raw = subarray(state, length + offset)\n\n when 0xf0, 0xf7 # \n length = readVarInt(state)\n\n offset = state.position - start\n state.position = start\n raw = subarray(state, length + offset)\n\n else # channel event\n if (type & 0x80) is 0\n # running status - reuse lastEventType as the event type.\n #\teventTypeByte is actually the first parameter\n param1 = type\n type = state.lastEventType\n else\n param1 = readInt8(state)\n # State is updated with last event type\n state.lastEventType = type\n\n offset = state.position - start\n state.position = start\n\n switch (type >> 4)\n # Program Change and Channel Pressure only have one data byte\n when 0xc, 0xd\n raw = subarray(state, offset)\n else # The rest have two\n raw = subarray(state, offset + 1)\n\n # type may be the running status from state in which case it won't match the\n # first data bype\n return {\n deltaTime: deltaTime\n type: type\n data: raw\n }\n\n# temporary helper to read all events in a single track\nprocessEvents = (track) ->\n position = 0\n lastEventType = 0\n {data} = track\n length = data.length\n\n i = 0\n while track.position < length && i < 1000 # safety valve\n i++\n readEvent(track)\n\n# Move the next event into the current property of state, advancing track state\n# and resetting ticks\nadvanceTrack = (state) ->\n state.current = readEvent(state)\n state.ticks = 0\n\nnextTrackEvent = (state) ->\n {formatType, tracks} = state\n\n if formatType is 0 or formatType is 1\n minTicks = Infinity\n nextTrack = undefined\n trackIndex = 0\n\n # Find track with minimum delta time\n tracks.forEach (track, i) ->\n at = track.current.deltaTime - track.ticks\n\n if at < minTicks\n trackIndex = i\n minTicks = at\n nextTrack = track\n\n # Read the event\n if nextTrack\n # Update ticks\n tracks.forEach (track) ->\n track.ticks += minTicks\n\n # Advance the track\n event = nextTrack.current\n advanceTrack nextTrack\n\n # adjust delta time on event\n event.deltaTime = minTicks\n event.track = trackIndex\n\n return event\n else\n # Independent tracks\n throw \"MIDI format type #{formatType} not supported yet\"\n\nmodule.exports = MidiFile = (data) ->\n lastEventType = 0\n\n self =\n data: data\n formatType: -1\n position: 0\n ticksPerBeat: -1\n tracks: undefined\n\n header = readChunk self\n\n if header.id != 'MThd' or header.length != 6\n throw 'MIDI header not found'\n\n self.formatType = readInt16(header)\n trackCount = readInt16(header)\n timeDivision = readInt16(header)\n\n if timeDivision & 0x8000\n throw 'Expressing time division in SMTPE frames is not supported yet'\n else\n self.ticksPerBeat = timeDivision\n\n # Need track data so we can process track events\n self.tracks = [0...trackCount].map (i) ->\n trackChunk = readChunk(self)\n if trackChunk.id != 'MTrk'\n throw 'Unexpected chunk - expected MTrk, got ' + trackChunk.id\n\n # Each track has its own data and position\n track =\n data: trackChunk.data\n position: 0\n ticks: 0\n lastEventType: 0\n\n # Initialize the track state with the first event\n advanceTrack track\n\n return track\n\n return self\n\nObject.assign MidiFile, {\n nextTrackEvent\n parseEvent\n readEvent\n readInt8\n readInt16\n readInt24\n readInt32\n readVarInt\n readAscii\n}\n"
},
"lib/midi2composer.coffee": {
"content": "# Status: Copied from audio-builder improvements should be made there\n\n{nextTrackEvent, parseEvent} = MIDIFile = require \"./midi-file\"\n\nmicrosecondsPerMinute = 60000000\n\n# Map GM program change to composer instruments\n# 1-128 on Wikipedia https://en.wikipedia.org/wiki/General_MIDI#Program_change_events\n# 0-127 here\n\n# Returns [scale degree, accidental]\npitchToStaffNote = (n) ->\n r = mod n, 12\n o = Math.floor(n/12)\n\n staffNote = inverseScale[r]\n if staffNote?\n a = 0\n else\n staffNote = inverseScale[r - 1]\n a = 1\n\n [o * 7 + staffNote, a]\n\nmod = (n, k) ->\n (n % k + k) % k\n\nscale =\n \"-7\": # Cb\n 0: -1\n 1: 1\n 2: 3\n 3: 4\n 4: 6\n 5: 8\n 6: 10\n \"-6\": # Gb / eb\n 0: -1\n 1: 1\n 2: 3\n 3: 5\n 4: 6\n 5: 8\n 6: 10\n \"-5\": # Db / bb\n 0: 0\n 1: 1\n 2: 3\n 3: 5\n 4: 6\n 5: 8\n 6: 10\n \"-4\": # Ab / f\n 0: 0\n 1: 1\n 2: 3\n 3: 5\n 4: 7\n 5: 8\n 6: 10\n \"-3\": # Eb / c\n 0: 0\n 1: 2\n 2: 3\n 3: 5\n 4: 7\n 5: 8\n 6: 10\n \"-2\": # Bb / g\n 0: 0\n 1: 2\n 2: 3\n 3: 5\n 4: 7\n 5: 9\n 6: 10\n \"-1\": # F / d\n 0: 0\n 1: 2\n 2: 4\n 3: 5\n 4: 7\n 5: 9\n 6: 10\n 0: # C / a\n 0: 0\n 1: 2\n 2: 4\n 3: 5\n 4: 7\n 5: 9\n 6: 11\n 1: # G / e\n 0: 0\n 1: 2\n 2: 4\n 3: 6\n 4: 7\n 5: 9\n 6: 11\n 2: # D / b\n 0: 1\n 1: 2\n 2: 4\n 3: 6\n 4: 7\n 5: 9\n 6: 11\n 3: # A / f#\n 0: 1\n 1: 2\n 2: 4\n 3: 6\n 4: 8\n 5: 9\n 6: 11\n 4: # E / c#\n 0: 1\n 1: 3\n 2: 4\n 3: 6\n 4: 8\n 5: 9\n 6: 11\n 5: # B / g#\n 0: 1\n 1: 3\n 2: 4\n 3: 6\n 4: 8\n 5: 10\n 6: 11\n 6: # F# / d#\n 0: 1\n 1: 3\n 2: 5\n 3: 6\n 4: 8\n 5: 10\n 6: 11\n 7: # C#\n 0: 1\n 1: 3\n 2: 5\n 3: 6\n 4: 8\n 5: 10\n 6: 12\n\ninverseScale = Object.keys(scale[0]).reduce (m, key) ->\n value = scale[0][key]\n m[value] = key|0\n return m\n, {}\n\n\nprograms =\n 0: 1 # Acoustic Grand\n 1: 1 # Bright Acoustic\n 2: 1 # Electric Grand\n 3: 1 # Honky-Tonk Piano\n 4: 1 # Electric Piano 1\n 5: 1 # Electric Piano 2\n 6: 1 # Harpsichord\n 7: 1 # Clavi\n 8: 6 # Celesta\n 9: 6 # Glockenspiel\n 10: 6 # Music Box\n 11: 6 # Vibraphone\n 12: 6 # Marimba\n 13: 6 # Xylophone\n 14: 6 # Tubular Bells\n 15: 6 # Dulcimer\n 16: 7 # Drawbar organ\n 17: 7 # Percussive Organ\n 18: 7 #17 # Rock Organ\n 19: 7 # Church Organ\n 20: 7 # Reed Organ\n 21: 7 # Accordion\n 22: 7 # Harmonica\n 23: 7 # Tango Accordion\n 24: 19 # Acoustic Guitar nylon\n 25: 2 # Acoustic Guitar steel\n 26: 2 # Electric Guitar (Jazz)\n 27: 2 # Electric Guitar (clean)\n 28: 2 # Electric Guitar (muted)\n 29: 2 # Overdriven Guitar\n 30: 19 # Distortion Guitar\n 31: 6 # Guitar Harmonics\n 32: 3 # Acoustic Bass\n 33: 3 # Electric Bass (finger)\n 34: 3 # Electric Bass (pick)\n 35: 3 # Fretless bass\n 36: 3 # Slap Bass 1\n 37: 3 # Slap Bass 2\n 38: 3 # Synth Bass 1\n 39: 3 # Synth Bass 2\n 40: 0 # Violin\n 41: 16 # Viola\n 42: 0 # Cello\n 43: 17 # Contrabass\n 44: 4 # Tremolo strings\n 45: 19 # Pizzicato Strings\n 46: 6 # Orchestral Harp\n 47: 10 # Timpani\n 48: 18 # String Ensemble 1\n 49: 18 # String Ensemble 2\n 50: 18 # Synth Strings 1\n 51: 18 # Synth Strings 2\n 52: 17 # Choir Aahs\n 53: 16 # Voice Aahs\n 54: 16 # Synth Voice\n 55: 5 # Orchetra hit\n 56: 4 # Trumpet\n 57: 4 # Trombone\n 58: 4 # Tuba\n 59: 4 # Muted Trumpet\n 60: 4 # French Horn\n 61: 4 # Brass Section\n 62: 4 # Synth Brass\n 63: 4 # Synth Brass 2\n 64: 17 # Soprano Sax\n 65: 15 #4 # Alto Sax\n 66: 4 # Tenor Sax\n 67: 4 # Baritone Sax\n 68: 4 # Oboe\n 69: 4 # English Horn\n 70: 4 # Bassoon\n 71: 4 # Clarinet\n 72: 2 #4 # Piccolo\n 73: 6 # Flute\n 74: 1 # Recorder\n 75: 0 # Pan Flute\n 76: 0 # Blown Bottle\n 77: 18 # Shakuhachi\n 78: 18 # Whistle\n 79: 6 # Ocarina\n 80: 0 # Syth Lead 1 (square)\n 81: 0 # Synth Lead 2 (sawtooth)\n 82: 0 # Lead 3 (caliope)\n 83: 0 # Lead 4 (chiff)\n 84: 0 # Lead 5 (charang)\n 85: 16 # Lead 6 (voice)\n 86: 0 # Lead 7 (fifths)\n 87: 0 # Bass + Lead\n 88: 13 # Pad 1 (new age)\n 89: 18 # Pad 2 (warm)\n 90: 18 # Pad 3 (polysynth)\n 91: 7 # Pad 4 (choir)\n 92: 7 # Pad 5 (bowed)\n 93: 18 # Pad 6 (metallic)\n 94: 7 # Pad 7 (halo)\n 95: 18 # Pad 8 Sweep\n 96: 13 # FX 1 Rain\n 97: 14 # FX 2 (soundtrack)\n 98: 6 # FX 3 (crystal)\n 99: 13 # FX 4 (atmosphere)\n 100: 6 # 2 # FX 5 (brightness)\n 101: 14 # Goblins\n 102: 17 # FX (echoes)\n 103: 13 # FX Sci Fi\n 104: 17 # Sitar\n 105: 19 # Banjo\n 106: 19 # Shamisen\n 107: 19 # Koto\n 108: 19 # Kalimba\n 109: 17 # Bag Pipe\n 110: 1 # Fiddle\n 111: 4 # Shanai\n 112: 6 # Tinkle Bell\n 113: 6 # Agogo\n 114: 1 # Steel drums\n 115: 10 # Woodblock\n 116: 8 # Taiko drum\n 117: 8 # Melodic Tom\n 118: 8 # Synth Drum\n 119: 12 # Reverse Cymbal\n 120: 17 # Guitar Fret Noise\n 121: 13 # Breath Noise\n 122: 13 # Seashore\n 123: 4 # Bird Tweet\n 124: 0 # Telephone Ring\n 125: 9 # Helicopter\n 126: 9 # Applause\n 127: 9 # Gunshot\n\ndrums = \n 12: [] # Mute sordo?\n 20: [[10, 10]] # Metronome Click?\n 27: [[20, 7]] # Brush Slap Swirl?\n 28: [[20, 7]] # Brush Slap?\n 29: [] # TODO\n 30: [] # TODO\n 31: [[10, 7]] # Sticks\n 32: [[10, 10]] # Metronome Click?\n 33: [[20, 0], [10, 4]] # Rim Shot\n 34: [[8, -4]] # Acoustic Bass\n 35: [[8, -5]]# Electric Bass Drum\n 36: [[10, 8]] # Side Stick\n 37: [[9, 0]] # Acoustic Snare\n 38: [[11, 0]] # Hand Clap\n 39: [[20, 0]] # Electric Snare\n 40: [[8, 1]] # Low Floor Tom\n 41: [[12, 11]] # Closed Hat\n 42: [[8, 3]] # High Floor Tom\n 43: [[12, -1]] # Pedal High Hat\n 44: [[8, 4]] # Low Tom\n 45: [[12, -3]] # Open Hat\n 46: [[8, 6]] # Low Mid Tom\n 47: [[8, 7]] # Mid High Tom\n 48: [[12, -11]] # Crash Cymbal 1\n 49: [[8, 8]] # High Tom\n 50: [[12, -9]] # Ride Cymbal\n 51: [[12, -12], [12, -10]] # Chinese Cymbal\n 52: [[6, 7]] # Ride Bell\n 53: [[15, 16], [12, 16]] # Tambourine\n 54: [[12 ,-7]] # Splash Cymbal\n 55: [[10, -4]] # Cowbell\n 56: [[12, -14]] # Crash Cymbal 2\n 57: [[15, -1]] # Vibra Slap\n 58: [] # TODO\n 59: [[8, 8]] # High Bongo\n 60: [[8, 6]] # Low Bongo\n 61: [[8, -2], [8, 6]] # Mute High Conga\n 62: [[8, -2]] # Open High Conga\n 63: [[8, -6]] # Low Conga\n 64: [[10, 5]] # High Timbale\n 65: [[10, -5]] # Low Timbale\n 66: [[6, 4]] # High Agogo\n 67: [[6, -2]] # Low Agogo\n 68: [[11, 10]] # Cabasa \n 69: [[11, 7]] # Maracas\n 70: [[18, 14]] # Short high whistle\n 71: [] # TODO\n 72: [] # TODO\n 73: [] # TODO\n 74: [] # TODO\n 75: [[10, 0]] # High Woodblock\n 76: [[10, -5]] # Low Woodblock\n 77: [] # TODO\n 78: [] # TODO\n 79: [] # TODO\n 80: [[12, 2]] # Open Triangle\n 81: [] # TODO\n 82: [] # TODO\n 83: [[6, -14], [6, -7], [6, 0], [6, 7], [6, 14]] # Bell Tree\n 84: [] # TODO\n 85: [] # TODO\n 86: [] # TODO\n 87: [[11, -7], [11, 0], [11, 7]] # Applause\n 88: [] # ???\n 89: [] # ???\n 90: []\n 91: [] # ???\n 93: [] # ???\n 94: []\n 95: []\n 96: []\n 121: [] # ???\n\nmissing = {}\n\nmodule.exports = (buffer) ->\n midiFile = MIDIFile(buffer)\n\n events = []\n\n while event = nextTrackEvent(midiFile)\n events.push parseEvent event\n\n midi: midiFile\n ticksPerBeat = midiFile.ticksPerBeat\n \n acc = 0\n section =\n name: \"Section 1\"\n tempo: 120\n notes: []\n\n sections = [section]\n\n channels = [0...16].map ->\n instrument: 0\n\n data = events.map (e) ->\n {deltaTime} = e\n acc += deltaTime\n e.t = acc\n e\n\n maxT = acc / ticksPerBeat\n\n n = 1\n acc = 0\n data.forEach ({t, noteNumber, subtype, microsecondsPerBeat, channel, programNumber, velocity}) ->\n beat = t / ticksPerBeat - acc\n\n switch subtype\n when \"setTempo\"\n if section.notes.length\n acc += beat\n section.length = beat\n section =\n name: \"Section #{++n}\"\n tempo: Math.round microsecondsPerMinute / microsecondsPerBeat\n notes: []\n sections.push section\n else\n section.tempo = Math.round microsecondsPerMinute / microsecondsPerBeat\n when \"programChange\"\n instrument = programs[programNumber]\n if instrument?\n channels[channel] = programs[programNumber]\n else\n missing[programNumber] = true\n when \"noteOn\"\n return if velocity is 0\n # [beat, staffNote, accidental, instrument]\n [staffNote, accidental] = pitchToStaffNote(noteNumber - 60)\n if channel is 9 # drums\n mappings = drums[noteNumber]\n\n if !mappings\n mappings = drums[noteNumber] = []\n missing.drums ?= {}\n missing.drums[noteNumber] = true\n\n # Drums map into one or more note events\n mappings.forEach ([instrument, staffNote]) ->\n section.notes.push [beat, staffNote, 0, instrument]\n else\n instrument = channels[channel]\n\n if instrument is 3 # bass\n staffNote += 7 # octave up\n\n section.notes.push [beat, staffNote, accidental, instrument]\n\n section.length = maxT + 4\n\n console.warn \"MISSING:\", missing\n\n return {\n sections: sections\n version: 2\n }\n"
},
"views/settings.coffee": {
"content": "{Jadelet, Modal, Observable} = system.ui\n\nTemplate = Jadelet.exec \"\"\"\nsection.settings\n h2 ⚙️ Settings\n button.close(click=@close) X\n table\n thead\n tr\n th Name\n th Volume\n th Pan\n th Pitch Shift\n tbody\n @items\n\"\"\"\n\nRowTemplate = Jadelet.exec \"\"\"\ntr\n td.sprite\n img(src=@url)\n @name\n td.input\n input(type=\"range\" min=0 max=2 step=0.01 value=@volume)\n input(type=\"number\" min=0 max=2 step=0.01 value=@volume)\n td.input\n input(type=\"range\" min=-1 max=1 step=0.01 value=@pan)\n input(type=\"number\" min=-1 max=1 step=0.01 value=@pan)\n td.input\n input(type=\"range\" min=-12 max=12 step=1 value=@pitchShift)\n input(type=\"number\" min=-12 max=12 step=1 value=@pitchShift)\n\"\"\"\n\nmodule.exports = (player) ->\n player.showSpriteConfig ?= ->\n Modal.show self.element\n\n self =\n close: -> Modal.hide()\n items: ->\n player.song().settings().map RowTemplate\n\n element: null\n\n self.element = Template self\n\n return self\n"
}
},
"distribution": {
"CHANGELOG": {},
"NOTES": {},
"README": {},
"TODO": {},
"data/demo": {
"content": "module.exports = {\"channels\":[{\"data\":{\"0\":0,\"8\":3,\"16\":0,\"24\":4,\"32\":1,\"40\":5,\"48\":8,\"60\":9,\"64\":8,\"76\":6}},{\"data\":{\"0\":2,\"4\":2,\"8\":2,\"12\":2,\"16\":2,\"20\":2,\"24\":2,\"28\":2,\"48\":2,\"52\":2,\"56\":2,\"64\":2,\"68\":2,\"72\":2,\"76\":2}},{\"data\":{}},{\"data\":{}}],\"patterns\":[{\"beats\":8,\"notes\":[[0,10,0],[0,7,0],[3.25,10,0],[3.75,10,0],[4,9,0],[4,5,0],[4.75,5,0],[7,5,0],[7.5,7,0],[3.5,12,0],[7,-4,0],[7.5,-2,0]]},{\"beats\":8,\"notes\":[[0,5,0],[0.5,5,0],[0.5,3,0],[1,2,0],[1,5,0],[1.5,0,0],[1.5,5,0],[2,7,0],[2.5,8,0],[3,7,0],[3.5,5,0],[2,-1,0],[2.5,-1,0],[3,0,0],[3.5,2,0],[0,-9,2],[0.5,-9,2],[1,-9,2],[1.5,-9,2],[1.75,-5,2],[2.25,-5,2],[3.5,-5,2],[3,-5,2],[2.75,-5,2],[4,0,0],[4,3,0],[5,2,0],[5,5,0],[6,3,0],[6,7,0],[4,0,2],[4.5,0,2],[5,0,2],[5.5,0,2],[5.75,-2,2],[6.25,-2,2],[6.75,-2,2],[7,-2,2],[7.5,-2,2],[6.75,10,0],[6.75,7,0]]},{\"beats\":4,\"notes\":[[0,-9,2],[0.5,-9,2],[1,-9,2],[1.75,-9,2],[2.25,-9,2],[2.75,-9,2],[3,-9,2],[3.5,-9,2]]},{\"beats\":8,\"notes\":[[0,-1,0],[0,8,0],[3.25,8,0],[3.5,10,0],[3.75,8,0],[4,3,0],[4,7,0],[4.75,7,0],[4.75,15,0]]},{\"beats\":8,\"notes\":[[0,-1,0],[0,8,0],[3.25,8,0],[3.25,-1,0],[3.5,7,0],[3.5,-2,0],[3.75,5,0],[3.75,-4,0],[4,3,0],[4,-5,0]]},{\"beats\":8,\"notes\":[[0,12,0],[0.5,15,0],[0.5,10,0],[1,9,0],[1,15,0],[1.5,15,0],[1.5,7,0],[2,6,0],[2,14,0],[2.5,6,0],[3,7,0],[3.5,9,0],[3.5,12,0],[3,11,0],[2.5,12,0],[0,-3,2],[0.5,-3,2],[1,-3,2],[1.5,-3,2],[1.75,2,2],[2.25,2,2],[2.75,2,2],[3,2,2],[3.5,2,2],[4,-5,2],[4.5,-5,2],[5,-3,2],[5.5,-3,2],[6,-1,2],[6.5,-1,2],[7.25,-2,2],[7.25,2,0],[7.25,8,0],[7.25,10,0],[4,11,0],[4,14,0],[5,12,0],[5,15,0],[6,14,0],[6,17,0]]},{\"beats\":4,\"notes\":[[0,7,0],[0,-2,0],[0.25,5,0],[0.25,-4,0],[0.5,3,0],[0.5,-6,0]]},{\"beats\":4,\"notes\":[]},{\"beats\":12,\"notes\":[[0.5,12,0],[1.25,10,0],[1.25,19,0],[2,15,0],[2,7,0],[2.75,10,0],[2.75,3,0],[4,9,0],[4.5,9,0],[5,9,0],[5.5,9,0],[5.75,9,0],[5.75,17,0],[7,5,0],[7.5,7,0],[8,8,0],[4,5,0],[8.5,11,0],[8.5,15,0],[9.25,11,0],[9.25,8,0],[10,8,0],[10,-1,0],[10.75,3,0],[10.75,-4,0],[11.5,-1,0],[11.5,5,0]]},{\"beats\":4,\"notes\":[[0,-2,0],[0,7,0],[0,-9,2],[0.5,-9,2],[1,-9,2],[1.5,-9,2],[1.75,-2,2],[2.25,-2,2],[2.75,-2,2],[3,-2,2],[3.5,-2,2]]}],\"tempo\":\"120\"};"
},
"data/templates/about": {
"content": "\n"
},
"data/templates/actions": {
"content": "module.exports = {\n tempo: 120,\n beats: 8,\n redoDisabled: true,\n hiddenIfPurchased: function() {},\n noteControlElement: require(\"/views/note-control\")().element,\n stereoAnalyserElement: require(\"/views/stereo-analyser\")(new AudioContext).element,\n hiddenUnlessPurchased: function() {\n return \"hidden\";\n }\n};\n"
},
"data/templates/app": {
"content": "\n"
},
"data/templates/buy-now": {
"content": "\n"
},
"data/templates/donate": {
"content": "\n"
},
"data/templates/export": {
"content": "\n"
},
"data/templates/feedback-tab": {
"content": "\n"
},
"data/templates/help": {
"content": "\n"
},
"data/templates/login": {
"content": "\n"
},
"data/templates/note-control": {
"content": "\n"
},
"data/templates/note": {
"content": "module.exports = {\n note: \"C4\",\n accidental: \"♭\",\n instrument: \"dog\"\n};\n"
},
"data/templates/pattern": {
"content": "\n"
},
"data/templates/patterns": {
"content": "module.exports = {\n patterns: []\n};\n"
},
"data/templates/publish": {
"content": "\n"
},
"data/templates/staff": {
"content": "module.exports = {\n contextmenu: function(e) {\n return e.preventDefault();\n },\n selectionClass: \"set\"\n};\n"
},
"data/templates/tools": {
"content": "var Observable, Sample, samples;\n\nObservable = system.ui.Observable;\n\nSample = require(\"../../sample\");\n\nsamples = Observable([]);\n\nSample.loadPack().then(samples);\n\nmodule.exports = {\n samples: samples,\n activeToolIndex: Observable(),\n activeInstrument: Observable(),\n playNote: function() {}\n};\n"
},
"data/views/note-control": {
"content": "module.exports = {};\n"
},
"data/views/patterns": {
"content": "\n"
},
"data/views/size": {
"content": "\n"
},
"data/views/tools": {
"content": "var Observable;\n\nObservable = system.ui.Observable;\n\nmodule.exports = {\n activeToolIndex: Observable,\n samples: []\n};\n"
},
"lib/audio-context": {
"content": "var AudioContext;\n\nAudioContext = window.AudioContext || window.webkitAudioContext;\n\nmodule.exports = new AudioContext({\n sampleRate: 44100\n});\n"
},
"lib/clipboard-copy": {
"content": "\n/*\nCopy to clipboard.\n\nMove to system lib?\n\nStatus: Done\n */\nmodule.exports = function(str) {\n var el;\n el = document.createElement('textarea');\n el.value = str;\n el.setAttribute('readonly', '');\n el.style.position = 'absolute';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n return document.body.removeChild(el);\n};\n"
},
"lib/drop": {
"content": "\n/*\nHandle drop events. Will be replaced by system app drop event handling.\n\nStatus: Deprecated.\n */\nvar Drop, stopFn;\n\nstopFn = function(event, handler) {\n event.stopPropagation();\n event.preventDefault();\n return false;\n};\n\nDrop = function(el, handler) {\n el = document.documentElement;\n el.addEventListener(\"dragenter\", stopFn);\n el.addEventListener(\"dragover\", stopFn);\n el.addEventListener(\"dragleave\", stopFn);\n return el.addEventListener(\"drop\", function(event) {\n stopFn(event);\n return handler(event.dataTransfer.files);\n });\n};\n\nmodule.exports = Drop;\n"
},
"lib/encoder/index": {
"content": "\n/*\nEncode web audio buffer data in a variety of formats. Handles both stereo and\nmono.\n\nMP3, WAV\n\nUse web workers where appropriate.\n\nStatus: Acceptable\n */\nvar audioBufferToChannelData, audioBufferToMP3, audioBufferToWav, audioChannelDataToInt16, fnToWorker, mp3Encode, remoteWorker;\n\nmp3Encode = function(data) {\n return remoteWorker(\"lame-worker2.js\").then(function(worker) {\n return new Promise(function(resolve, reject) {\n worker.postMessage(data);\n return worker.onmessage = function(e) {\n return resolve(e.data);\n };\n });\n });\n};\n\nremoteWorker = function(url) {\n return fetch(url).then(function(response) {\n return response.arrayBuffer();\n }).then(function(buffer) {\n var blob;\n blob = new Blob([buffer], {\n type: \"application/javascript\"\n });\n return URL.createObjectURL(blob);\n }).then(function(url) {\n return new Worker(url);\n });\n};\n\naudioBufferToChannelData = function(audioBuffer) {\n var channels;\n channels = audioBuffer.numberOfChannels;\n if (channels === 2) {\n return [audioBuffer.getChannelData(0), audioBuffer.getChannelData(1)];\n } else {\n return [audioBuffer.getChannelData(0)];\n }\n};\n\naudioChannelDataToInt16 = function(buffer) {\n var bufferLength, data, i, max, min, sample;\n min = Math.min, max = Math.max;\n bufferLength = buffer.length;\n data = new Int16Array(bufferLength);\n i = 0;\n while (i < bufferLength) {\n sample = max(-1, min(1, buffer[i]));\n if (sample < 0) {\n sample *= 0x8000;\n } else {\n sample *= 0x7FFF;\n }\n data[i] = sample;\n i++;\n }\n return data;\n};\n\naudioBufferToMP3 = function(audioBuffer) {\n var bufferWorker;\n if (!audioBuffer) {\n throw new Error(\"Must pass an AudioBuffer to encode .mp3\");\n }\n bufferWorker = fnToWorker(audioChannelDataToInt16);\n return Promise.all(audioBufferToChannelData(audioBuffer).map(function(channelBuffer) {\n return bufferWorker(channelBuffer);\n })).then(function(channelData) {\n return mp3Encode({\n channelData: channelData\n });\n });\n};\n\naudioBufferToWav = function(audioBuffer) {\n if (!audioBuffer) {\n throw new Error(\"Must pass an AudioBuffer to encode .wav\");\n }\n return new Promise(function(resolve, reject) {\n var url, worker, workerSource;\n workerSource = new Blob([PACKAGE.distribution[\"lib/encoder/wave-worker\"].content], {\n type: \"application/javascript\"\n });\n url = URL.createObjectURL(workerSource);\n worker = new Worker(url);\n worker.onmessage = function(e) {\n resolve(new Blob([e.data.buffer], {\n type: \"audio/wav\"\n }));\n return URL.revokeObjectURL(url);\n };\n return worker.postMessage({\n pcmArrays: audioBufferToChannelData(audioBuffer),\n config: {\n sampleRate: audioBuffer.sampleRate\n }\n });\n });\n};\n\nfnToWorker = function(fn) {\n return function(data) {\n return new Promise(function(resolve, reject) {\n var url, worker, workerSource;\n workerSource = new Blob([\"self.onmessage = function (e) {\\n self.postMessage((\" + (fn.toString()) + \")(e.data));\\n self.close();\\n};\"], {\n type: \"application/javascript\"\n });\n url = URL.createObjectURL(workerSource);\n worker = new Worker(url);\n worker.onmessage = function(e) {\n resolve(e.data);\n return URL.revokeObjectURL(url);\n };\n return worker.postMessage(data);\n });\n };\n};\n\nmodule.exports = {\n mp3Encode: mp3Encode,\n audioBufferToMP3: audioBufferToMP3,\n audioBufferToWav: audioBufferToWav,\n audioChannelDataToInt16: audioChannelDataToInt16,\n fnToWorker: fnToWorker\n};\n"
},
"lib/encoder/wave-worker": {
"content": "// https://stackoverflow.com/a/42632646/68210\r\n\r\nself.onmessage = function( e ){\r\n var wavPCM = new WavePCM( e['data']['config'] );\r\n wavPCM.record( e['data']['pcmArrays'] );\r\n wavPCM.requestData();\r\n};\r\n\r\nvar WavePCM = function( config ){\r\n this.sampleRate = config['sampleRate'] || 48000;\r\n this.bitDepth = config['bitDepth'] || 16;\r\n this.recordedBuffers = [];\r\n this.bytesPerSample = this.bitDepth / 8;\r\n};\r\n\r\nWavePCM.prototype.record = function( buffers ){\r\n this.numberOfChannels = this.numberOfChannels || buffers.length;\r\n var bufferLength = buffers[0].length;\r\n var reducedData = new Uint8Array( bufferLength * this.numberOfChannels * this.bytesPerSample );\r\n\r\n // Interleave\r\n for ( var i = 0; i < bufferLength; i++ ) {\r\n for ( var channel = 0; channel < this.numberOfChannels; channel++ ) {\r\n\r\n var outputIndex = ( i * this.numberOfChannels + channel ) * this.bytesPerSample;\r\n var sample = buffers[ channel ][ i ];\r\n\r\n // Check for clipping\r\n if ( sample > 1 ) {\r\n sample = 1;\r\n }\r\n\r\n else if ( sample < -1 ) {\r\n sample = -1;\r\n }\r\n\r\n // bit reduce and convert to uInt\r\n switch ( this.bytesPerSample ) {\r\n case 4:\r\n sample = sample * 2147483648;\r\n reducedData[ outputIndex ] = sample;\r\n reducedData[ outputIndex + 1 ] = sample >> 8;\r\n reducedData[ outputIndex + 2 ] = sample >> 16;\r\n reducedData[ outputIndex + 3 ] = sample >> 24;\r\n break;\r\n\r\n case 3:\r\n sample = sample * 8388608;\r\n reducedData[ outputIndex ] = sample;\r\n reducedData[ outputIndex + 1 ] = sample >> 8;\r\n reducedData[ outputIndex + 2 ] = sample >> 16;\r\n break;\r\n\r\n case 2:\r\n sample = sample * 32768;\r\n reducedData[ outputIndex ] = sample;\r\n reducedData[ outputIndex + 1 ] = sample >> 8;\r\n break;\r\n\r\n case 1:\r\n reducedData[ outputIndex ] = ( sample + 1 ) * 128;\r\n break;\r\n\r\n default:\r\n throw \"Only 8, 16, 24 and 32 bits per sample are supported\";\r\n }\r\n }\r\n }\r\n\r\n this.recordedBuffers.push( reducedData );\r\n};\r\n\r\nWavePCM.prototype.requestData = function(){\r\n var bufferLength = this.recordedBuffers[0].length;\r\n var dataLength = this.recordedBuffers.length * bufferLength;\r\n var headerLength = 44;\r\n var wav = new Uint8Array( headerLength + dataLength );\r\n var view = new DataView( wav.buffer );\r\n\r\n view.setUint32( 0, 1380533830, false ); // RIFF identifier 'RIFF'\r\n view.setUint32( 4, 36 + dataLength, true ); // file length minus RIFF identifier length and file description length\r\n view.setUint32( 8, 1463899717, false ); // RIFF type 'WAVE'\r\n view.setUint32( 12, 1718449184, false ); // format chunk identifier 'fmt '\r\n view.setUint32( 16, 16, true ); // format chunk length\r\n view.setUint16( 20, 1, true ); // sample format (raw)\r\n view.setUint16( 22, this.numberOfChannels, true ); // channel count\r\n view.setUint32( 24, this.sampleRate, true ); // sample rate\r\n view.setUint32( 28, this.sampleRate * this.bytesPerSample * this.numberOfChannels, true ); // byte rate (sample rate * block align)\r\n view.setUint16( 32, this.bytesPerSample * this.numberOfChannels, true ); // block align (channel count * bytes per sample)\r\n view.setUint16( 34, this.bitDepth, true ); // bits per sample\r\n view.setUint32( 36, 1684108385, false); // data chunk identifier 'data'\r\n view.setUint32( 40, dataLength, true ); // data chunk length\r\n\r\n for (var i = 0; i < this.recordedBuffers.length; i++ ) {\r\n wav.set( this.recordedBuffers[i], i * bufferLength + headerLength );\r\n }\r\n\r\n self.postMessage( wav, [wav.buffer] );\r\n self.close();\r\n};\n"
},
"lib/extensions": {
"content": "var Observable, extend, flat,\n __slice = [].slice;\n\nObservable = system.ui.Observable;\n\nextend = function() {\n var sources, target;\n target = arguments[0], sources = 2 <= arguments.length ? __slice.call(arguments, 1) : [];\n return sources.forEach(function(source) {\n return Object.keys(source).forEach(function(key) {\n if (Object.prototype.hasOwnProperty.call(source, key)) {\n return target[key] = source[key];\n }\n });\n });\n};\n\nif (Object.assign == null) {\n Object.assign = extend;\n}\n\nObject.assign(global, {\n Model: require(\"./model\"),\n Observable: Observable,\n defaults: function(target, values) {\n Object.keys(values).forEach(function(key) {\n if (!Object.prototype.hasOwnProperty.call(target, key)) {\n return target[key] = values[key];\n }\n });\n return target;\n }\n});\n\nif (!Array.prototype.flat) {\n flat = function(array, depth, flattened) {\n return array.forEach(function(el) {\n if (Array.isArray(el) && depth > 0) {\n return flat(el, depth - 1, flattend);\n } else {\n return flattend.push(el);\n }\n });\n };\n Array.prototype.flat = function(depth) {\n var flattend;\n flattend = [];\n flat(this, Math.floor(depth) || 1, flattened);\n return flattend;\n };\n}\n"
},
"lib/hotkeys": {
"content": "\n/*\nBind hotekeys. This will be replaced by the system `app` hotkey bindings at some\npoint.\n\nStatus: Deprecated\n */\nvar Mousetrap;\n\nMousetrap = require(\"./mousetrap\");\n\nmodule.exports = function(I, self) {\n return self.extend({\n addHotkey: function(key, method) {\n return Mousetrap.bind(key, function(e) {\n e.preventDefault();\n if (typeof method === \"function\") {\n return method({\n editor: self\n });\n } else {\n return self[method]();\n }\n });\n }\n });\n};\n"
},
"lib/legacy/channel": {
"content": "\n/*\nChannel\n=======\n\nA channel holds a sequence of patterns. The patterns are stored in the `data`\ntable at beat keys with `patternId` values.\n\nStatus: Legacy\n */\nvar max, min, overlap;\n\nmodule.exports = function(I, self) {\n var patternStarts;\n if (I == null) {\n I = {};\n }\n if (self == null) {\n self = Model(I);\n }\n defaults(I, {\n data: {}\n });\n patternStarts = function(patterns) {\n return Object.keys(I.data).map(function(start) {\n var end, pattern, patternIndex;\n start = parseInt(start, 10);\n patternIndex = parseInt(I.data[start], 10);\n pattern = patterns[patternIndex];\n end = start + pattern.size();\n return [start, end, pattern, patternIndex];\n });\n };\n self.extend({\n patterns: patternStarts,\n canSet: function(beat, patternIndex, patterns) {\n var size, toInsert;\n size = patterns[patternIndex].size();\n toInsert = [beat, beat + size];\n return !patternStarts(patterns).some(function(segment) {\n return overlap(toInsert, segment);\n });\n },\n patternDataAt: function(beat, patterns) {\n return patternStarts(patterns).filter(function(_arg) {\n var end, start;\n start = _arg[0], end = _arg[1];\n return (start <= beat && beat < end);\n });\n },\n patternAt: function(beat, patterns) {\n return self.patternDataAt(beat, patterns).map(function(_arg) {\n var end, pattern, patternIndex, start;\n start = _arg[0], end = _arg[1], pattern = _arg[2], patternIndex = _arg[3];\n return patternIndex;\n })[0];\n },\n setPattern: function(beat, patternIndex) {\n return I.data[beat] = patternIndex;\n },\n removePattern: function(beat, patterns) {\n var patternsAtBeat;\n patternsAtBeat = patternStarts(patterns).filter(function(_arg) {\n var end, start;\n start = _arg[0], end = _arg[1];\n return (start <= beat && beat < end);\n });\n patternsAtBeat.forEach(function(_arg) {\n var start;\n start = _arg[0];\n return delete I.data[start];\n });\n return patternsAtBeat.length > 0;\n },\n upcomingNotes: function(t, dt, patterns) {\n return patternStarts(patterns).filter(function(_arg) {\n var end, pattern, start;\n start = _arg[0], end = _arg[1], pattern = _arg[2];\n return ((start <= t && t < end)) || ((t <= start && start < t + dt));\n }).map(function(_arg) {\n var end, offset, pattern, start;\n start = _arg[0], end = _arg[1], pattern = _arg[2];\n offset = t - start;\n return pattern.upcomingNotes(offset, dt);\n }).flat();\n },\n size: function(patterns) {\n return patternStarts(patterns).reduce(function(n, _arg) {\n var end, start;\n start = _arg[0], end = _arg[1];\n return max(n, end);\n }, 0);\n }\n });\n return self;\n};\n\nmin = Math.min, max = Math.max;\n\noverlap = function(_arg, _arg1) {\n var a1, a2, b1, b2;\n a1 = _arg[0], a2 = _arg[1];\n b1 = _arg1[0], b2 = _arg1[1];\n return max(0, min(a2, b2) - max(a1, b1)) > 0;\n};\n"
},
"lib/legacy/gist": {
"content": "\n/*\nGithub gist loading and saving. Github no longer allows saving anonymous gists\nso this is only used for loading the previously saved ones.\n\nStatus: Legacy\n */\nvar base;\n\nbase = \"https://api.github.com/gists\";\n\nmodule.exports = {\n load: function(gistId) {\n return fetch(\"\" + base + \"/\" + gistId).then(function(data) {\n return data.json();\n }).then(function(data) {\n var _ref, _ref1;\n data = ((_ref = data.files[\"data.json\"]) != null ? _ref.content : void 0) || ((_ref1 = data.files[\"pattern0.json\"]) != null ? _ref1.content : void 0);\n if (data) {\n return JSON.parse(data);\n } else {\n return alert(\"Failed to load gist with id: \" + gistId);\n }\n });\n }\n};\n"
},
"lib/legacy/pattern": {
"content": "\n/*\nPattern\n\nHolds a pattern of notes. The previous version had a fixed number of patterns\nthat could be placed into channels. This version of the feature remains here to\nnot interfere with any future development. We use patterns in the newer version\nto create \"stamps\" to paint selections of notes onto the staff.\n\nStatus: Legacy\n */\nrequire(\"../extensions\");\n\nmodule.exports = function(I, self) {\n if (I == null) {\n I = {};\n }\n if (self == null) {\n self = Model(I);\n }\n defaults(I, {\n beats: 8,\n notes: []\n });\n I.beats = Number(I.beats);\n if (isNaN(I.beats)) {\n throw new Error(\"Beats must be a number\");\n }\n self.attrObservable(\"beats\");\n self.attrAccessor(\"notes\");\n return Object.assign(self, {\n addNote: function(note) {\n return I.notes.push(note);\n },\n removeNote: function(_arg) {\n var matched, note, time;\n time = _arg[0], note = _arg[1];\n matched = I.notes.filter(function(_arg1) {\n var n, t;\n t = _arg1[0], n = _arg1[1];\n return time === t && note === n;\n });\n return self.notes().remove(matched.last());\n },\n size: function() {\n return self.beats();\n },\n upcomingNotes: function(t, dt) {\n return self.notes().filter(function(_arg) {\n var time;\n time = _arg[0];\n if (dt > 0) {\n return (t <= time && time < t + dt);\n } else if (dt < 0) {\n return (t + dt < time && time <= t);\n }\n }).map(function(_arg) {\n var instrument, note, time;\n time = _arg[0], note = _arg[1], instrument = _arg[2];\n return [time - t, note, instrument];\n });\n }\n });\n};\n"
},
"lib/legacy/song": {
"content": "\n/*\nSong\n====\n\nA song has a tempo, a number of channels, and a number of patterns.\n\nPatterns are placed in the channels.\n\nThis is the original song format implementation, we need to keep it around for\nbackward compatibility.\n\nStatus: Legacy\n */\nvar Channel, Pattern,\n __slice = [].slice;\n\nChannel = require(\"./channel\");\n\nPattern = require(\"./pattern\");\n\nmodule.exports = function(I, self) {\n var initPatterns, numPatterns;\n if (I == null) {\n I = {};\n }\n if (self == null) {\n self = Model(I);\n }\n defaults(I, {\n channels: [\n {\n data: {\n 0: 0\n }\n }, {}, {}, {}\n ],\n patterns: [{}],\n tempo: 90\n });\n self.attrObservable(\"tempo\");\n self.attrModels(\"channels\", Channel);\n self.attrModels(\"patterns\", Pattern);\n numPatterns = 10;\n initPatterns = function() {\n var _i, _results;\n (function() {\n _results = [];\n for (var _i = 0; 0 <= numPatterns ? _i < numPatterns : _i > numPatterns; 0 <= numPatterns ? _i++ : _i--){ _results.push(_i); }\n return _results;\n }).apply(this).forEach(function(n) {\n var _base;\n return (_base = self.patterns())[n] != null ? _base[n] : _base[n] = Pattern();\n });\n return self.patterns(self.patterns().slice());\n };\n initPatterns();\n self.extend({\n channelPatterns: function(n) {\n return self.channels.get(n).patterns(self.patterns());\n },\n setPattern: function(channel, beat, patternIndex) {\n return self.channels.get(channel).setPattern(beat, patternIndex);\n },\n canSet: function(channel, beat, patternIndex) {\n return self.channels.get(channel).canSet(beat, patternIndex, self.patterns());\n },\n patternAt: function(channel, beat) {\n return self.channels.get(channel).patternAt(beat, self.patterns());\n },\n patternsDataAt: function(beat) {\n return self.channels().map(function(channel) {\n return channel.patternDataAt(beat, self.patterns())[0];\n });\n },\n removePattern: function(channel, beat) {\n return self.channels.get(channel).removePattern(beat, self.patterns());\n },\n upcomingNotes: function(t, dt) {\n var patterns;\n patterns = self.patterns();\n return self.channels.map(function(channel) {\n return channel.upcomingNotes(t, dt, patterns);\n }).flat();\n },\n size: function() {\n var _ref;\n return (_ref = Math.max).call.apply(_ref, [null].concat(__slice.call(self.channels().map(function(channel) {\n return channel.size(self.patterns());\n }))));\n },\n fromJSON: function(data) {\n var _i, _results;\n if (data.patterns != null) {\n self.patterns(data.patterns.map(function(data) {\n return Pattern(data);\n }));\n self.channels(data.channels.map(function(data) {\n return Channel(data);\n }));\n } else {\n self.patterns((function() {\n _results = [];\n for (var _i = 0; 0 <= numPatterns ? _i < numPatterns : _i > numPatterns; 0 <= numPatterns ? _i++ : _i--){ _results.push(_i); }\n return _results;\n }).apply(this).map(function(i) {\n if (i === 0) {\n return Pattern({\n beats: parseInt(data.beats, 10),\n notes: data.notes\n });\n } else {\n return Pattern();\n }\n }));\n self.channels([\n {\n data: {\n 0: 0\n }\n }, {}, {}, {}\n ].map(function(channelData) {\n return Channel(channelData);\n }));\n }\n initPatterns();\n self.tempo(data.tempo);\n return self;\n }\n });\n return self;\n};\n"
},
"lib/midi-input": {
"content": "\n/*\nMIDI Input\n\nConnect and map MIDI input into notes on the musical staff.\n\nStatus: Active\n */\nvar context, pitchToStaffNote, quantize, _ref;\n\ncontext = require(\"./audio-context\");\n\n_ref = require(\"./util\"), pitchToStaffNote = _ref.pitchToStaffNote, quantize = _ref.quantize;\n\nmodule.exports = function(I, self) {\n try {\n return navigator.requestMIDIAccess().then(function(midiAccess) {\n return Array.from(midiAccess.inputs.values()).forEach(function(input) {\n return input.onmidimessage = function(_arg) {\n var accidental, data, f, instrument, note, pitch, quantizedBeat, staffNote, velocity, _ref1;\n data = _arg.data;\n f = data[0], note = data[1], velocity = data[2];\n if (f === 0x90) {\n pitch = note - 60;\n _ref1 = pitchToStaffNote(pitch), staffNote = _ref1[0], accidental = _ref1[1];\n instrument = self.activeInstrument();\n quantizedBeat = quantize(self.playTime(), 0.25);\n self.addNote([quantizedBeat, staffNote, accidental, instrument]);\n context.resume();\n self.playNote(instrument, pitch);\n return self.triggerRerender();\n }\n };\n });\n });\n } catch (_error) {}\n};\n"
},
"lib/model": {
"content": "\n/*\nModel\n=====\n\nThe `Model` module provides helper methods to compose nested data models.\n\nWill be replaced by the model from `system` once that is more fully developed.\n\nStatus: Deprecated\n */\nvar Model, Observable, defaults, extend, getValue, isFn, setValue,\n __slice = [].slice;\n\nObservable = system.ui.Observable;\n\nmodule.exports = Model = function(I, self) {\n if (I == null) {\n I = {};\n }\n if (self == null) {\n self = {};\n }\n Object.assign(self, {\n\n /*\n `I` holds the instance state. It is generally considered private, but access\n is available for debugging and other purposes.\n */\n I: I,\n\n /*\n Generates a public jQuery style getter / setter method for each `String` argument.\n \n > #! example\n > myObject = Model\n > r: 255\n > g: 0\n > b: 100\n >\n > myObject.attrAccessor \"r\", \"g\", \"b\"\n >\n > myObject.r(254)\n */\n attrAccessor: function() {\n var attrNames;\n attrNames = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n attrNames.forEach(function(attrName) {\n return self[attrName] = function(newValue) {\n if (arguments.length > 0) {\n I[attrName] = newValue;\n return self;\n } else {\n return I[attrName];\n }\n };\n });\n return self;\n },\n\n /*\n Generates a public jQuery style getter method for each String argument.\n \n > #! example\n > myObject = Model\n > r: 255\n > g: 0\n > b: 100\n >\n > myObject.attrReader \"r\", \"g\", \"b\"\n >\n > [myObject.r(), myObject.g(), myObject.b()]\n */\n attrReader: function() {\n var attrNames;\n attrNames = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n attrNames.forEach(function(attrName) {\n return self[attrName] = function() {\n return I[attrName];\n };\n });\n return self;\n },\n\n /*\n Extends this object with methods from the passed in object. A shortcut for Object.extend(self, methods)\n \n > I =\n > x: 30\n > y: 40\n > maxSpeed: 5\n >\n > # we are using extend to give player\n > # additional methods that Model doesn't have\n > player = Model(I).extend\n > increaseSpeed: ->\n > I.maxSpeed += 1\n >\n > player.increaseSpeed()\n */\n extend: function() {\n var objects;\n objects = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n return Object.assign.apply(Object, [self].concat(__slice.call(objects)));\n },\n\n /*\n Includes a module in this object. A module is a constructor that takes two parameters, `I` and `self`\n \n > myObject = Model()\n > myObject.include(Bindable)\n \n > # now you can bind handlers to functions\n > myObject.bind \"someEvent\", ->\n > alert(\"wow. that was easy.\")\n */\n include: function() {\n var Module, modules, _i, _len;\n modules = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n for (_i = 0, _len = modules.length; _i < _len; _i++) {\n Module = modules[_i];\n Module(I, self);\n }\n return self;\n },\n\n /*\n Bind a data model getter/setter to an attribute. The data model is bound directly to\n the attribute and must be directly convertible to and from JSON.\n */\n attrData: function(name, DataModel) {\n I[name] = DataModel(I[name]);\n return Object.defineProperty(self, name, {\n get: function() {\n return I[name];\n },\n set: function(value) {\n return I[name] = DataModel(value);\n }\n });\n },\n\n /*\n Observe any number of attributes as observables. For each attribute name passed in we expose a public getter/setter method and listen to changes when the value is set.\n */\n attrObservable: function() {\n var names;\n names = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n names.forEach(function(name) {\n self[name] = Observable(I[name]);\n return self[name].observe(function(newValue) {\n return I[name] = newValue;\n });\n });\n return self;\n },\n observable: function(name, writer) {\n var o;\n o = Observable(writer(I[name]));\n o.observe(function(newValue) {\n return I[name] = writer(newValue);\n });\n return self[name] = function(v) {\n if (arguments.length > 0) {\n return o(writer(v));\n } else {\n return o();\n }\n };\n },\n\n /*\n Observe an attribute as a model. Treats the attribute given as an Observable\n model instance exposing a getter/setter method of the same name. The Model\n constructor must be passed explicitly.\n */\n attrModel: function(name, Model) {\n var model;\n model = Model(I[name]);\n self[name] = Observable(model);\n self[name].observe(function(newValue) {\n return I[name] = newValue.I;\n });\n return self;\n },\n\n /*\n Observe an attribute as an array of sub-models. This is the same as `attrModel`\n except the attribute is expected to be an array of models rather than a single one.\n */\n attrModels: function(name, Model) {\n var models;\n models = (I[name] || []).map(function(x) {\n return Model(x);\n });\n self[name] = Observable(models);\n self[name].observe(function(newValue) {\n return I[name] = newValue.map(function(instance) {\n return instance.I;\n });\n });\n return self;\n },\n\n /*\n Delegate methods to another target. Makes it easier to compose rather than extend.\n */\n delegate: function() {\n var names, to, _arg, _i;\n names = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), _arg = arguments[_i++];\n to = _arg.to;\n return names.forEach(function(name) {\n return Object.defineProperty(self, name, {\n get: function() {\n var receiver;\n receiver = getValue(self, to);\n return receiver[name];\n },\n set: function(value) {\n var receiver;\n receiver = getValue(self, to);\n return setValue(receiver, name, value);\n }\n });\n });\n },\n\n /*\n The JSON representation is kept up to date via the observable properites and resides in `I`.\n */\n toJSON: function() {\n return I;\n }\n });\n return self;\n};\n\nisFn = function(x) {\n return typeof x === 'function';\n};\n\ngetValue = function(receiver, property) {\n if (isFn(receiver[property])) {\n return receiver[property]();\n } else {\n return receiver[property];\n }\n};\n\nsetValue = function(receiver, property, value) {\n var target;\n target = receiver[property];\n if (isFn(target)) {\n return target.call(receiver, value);\n } else {\n return receiver[property] = value;\n }\n};\n\ndefaults = function() {\n var name, object, objects, target, _i, _len;\n target = arguments[0], objects = 2 <= arguments.length ? __slice.call(arguments, 1) : [];\n for (_i = 0, _len = objects.length; _i < _len; _i++) {\n object = objects[_i];\n for (name in object) {\n if (!target.hasOwnProperty(name)) {\n target[name] = object[name];\n }\n }\n }\n return target;\n};\n\nextend = Object.assign;\n\nObject.assign(Model, {\n Observable: Observable,\n defaults: defaults,\n extend: extend\n});\n"
},
"lib/mousetrap": {
"content": "/* mousetrap v1.6.3 craig.is/killing/mice */\r\n(function(q,u,c){function v(a,b,g){a.addEventListener?a.addEventListener(b,g,!1):a.attachEvent(\"on\"+b,g)}function z(a){if(\"keypress\"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return n[a.which]?n[a.which]:r[a.which]?r[a.which]:String.fromCharCode(a.which).toLowerCase()}function F(a){var b=[];a.shiftKey&&b.push(\"shift\");a.altKey&&b.push(\"alt\");a.ctrlKey&&b.push(\"ctrl\");a.metaKey&&b.push(\"meta\");return b}function w(a){return\"shift\"==a||\"ctrl\"==a||\"alt\"==a||\r\n\"meta\"==a}function A(a,b){var g,d=[];var e=a;\"+\"===e?e=[\"+\"]:(e=e.replace(/\\+{2}/g,\"+plus\"),e=e.split(\"+\"));for(g=0;g<e.length;++g){var m=e[g];B[m]&&(m=B[m]);b&&\"keypress\"!=b&&C[m]&&(m=C[m],d.push(\"shift\"));w(m)&&d.push(m)}e=m;g=b;if(!g){if(!p){p={};for(var c in n)95<c&&112>c||n.hasOwnProperty(c)&&(p[n[c]]=c)}g=p[e]?\"keydown\":\"keypress\"}\"keypress\"==g&&d.length&&(g=\"keydown\");return{key:m,modifiers:d,action:g}}function D(a,b){return null===a||a===u?!1:a===b?!0:D(a.parentNode,b)}function d(a){function b(a){a=\r\na||{};var b=!1,l;for(l in p)a[l]?b=!0:p[l]=0;b||(x=!1)}function g(a,b,t,f,g,d){var l,E=[],h=t.type;if(!k._callbacks[a])return[];\"keyup\"==h&&w(a)&&(b=[a]);for(l=0;l<k._callbacks[a].length;++l){var c=k._callbacks[a][l];if((f||!c.seq||p[c.seq]==c.level)&&h==c.action){var e;(e=\"keypress\"==h&&!t.metaKey&&!t.ctrlKey)||(e=c.modifiers,e=b.sort().join(\",\")===e.sort().join(\",\"));e&&(e=f&&c.seq==f&&c.level==d,(!f&&c.combo==g||e)&&k._callbacks[a].splice(l,1),E.push(c))}}return E}function c(a,b,c,f){k.stopCallback(b,\r\nb.target||b.srcElement,c,f)||!1!==a(b,c)||(b.preventDefault?b.preventDefault():b.returnValue=!1,b.stopPropagation?b.stopPropagation():b.cancelBubble=!0)}function e(a){\"number\"!==typeof a.which&&(a.which=a.keyCode);var b=z(a);b&&(\"keyup\"==a.type&&y===b?y=!1:k.handleKey(b,F(a),a))}function m(a,g,t,f){function h(c){return function(){x=c;++p[a];clearTimeout(q);q=setTimeout(b,1E3)}}function l(g){c(t,g,a);\"keyup\"!==f&&(y=z(g));setTimeout(b,10)}for(var d=p[a]=0;d<g.length;++d){var e=d+1===g.length?l:h(f||\r\nA(g[d+1]).action);n(g[d],e,f,a,d)}}function n(a,b,c,f,d){k._directMap[a+\":\"+c]=b;a=a.replace(/\\s+/g,\" \");var e=a.split(\" \");1<e.length?m(a,e,b,c):(c=A(a,c),k._callbacks[c.key]=k._callbacks[c.key]||[],g(c.key,c.modifiers,{type:c.action},f,a,d),k._callbacks[c.key][f?\"unshift\":\"push\"]({callback:b,modifiers:c.modifiers,action:c.action,seq:f,level:d,combo:a}))}var k=this;a=a||u;if(!(k instanceof d))return new d(a);k.target=a;k._callbacks={};k._directMap={};var p={},q,y=!1,r=!1,x=!1;k._handleKey=function(a,\r\nd,e){var f=g(a,d,e),h;d={};var k=0,l=!1;for(h=0;h<f.length;++h)f[h].seq&&(k=Math.max(k,f[h].level));for(h=0;h<f.length;++h)f[h].seq?f[h].level==k&&(l=!0,d[f[h].seq]=1,c(f[h].callback,e,f[h].combo,f[h].seq)):l||c(f[h].callback,e,f[h].combo);f=\"keypress\"==e.type&&r;e.type!=x||w(a)||f||b(d);r=l&&\"keydown\"==e.type};k._bindMultiple=function(a,b,c){for(var d=0;d<a.length;++d)n(a[d],b,c)};v(a,\"keypress\",e);v(a,\"keydown\",e);v(a,\"keyup\",e)}if(q){var n={8:\"backspace\",9:\"tab\",13:\"enter\",16:\"shift\",17:\"ctrl\",\r\n18:\"alt\",20:\"capslock\",27:\"esc\",32:\"space\",33:\"pageup\",34:\"pagedown\",35:\"end\",36:\"home\",37:\"left\",38:\"up\",39:\"right\",40:\"down\",45:\"ins\",46:\"del\",91:\"meta\",93:\"meta\",224:\"meta\"},r={106:\"*\",107:\"+\",109:\"-\",110:\".\",111:\"/\",186:\";\",187:\"=\",188:\",\",189:\"-\",190:\".\",191:\"/\",192:\"`\",219:\"[\",220:\"\\\\\",221:\"]\",222:\"'\"},C={\"~\":\"`\",\"!\":\"1\",\"@\":\"2\",\"#\":\"3\",$:\"4\",\"%\":\"5\",\"^\":\"6\",\"&\":\"7\",\"*\":\"8\",\"(\":\"9\",\")\":\"0\",_:\"-\",\"+\":\"=\",\":\":\";\",'\"':\"'\",\"<\":\",\",\">\":\".\",\"?\":\"/\",\"|\":\"\\\\\"},B={option:\"alt\",command:\"meta\",\"return\":\"enter\",\r\nescape:\"esc\",plus:\"+\",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?\"meta\":\"ctrl\"},p;for(c=1;20>c;++c)n[111+c]=\"f\"+c;for(c=0;9>=c;++c)n[c+96]=c.toString();d.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};d.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};d.prototype.trigger=function(a,b){if(this._directMap[a+\":\"+b])this._directMap[a+\":\"+b]({},a);return this};d.prototype.reset=function(){this._callbacks={};\r\nthis._directMap={};return this};d.prototype.stopCallback=function(a,b){if(-1<(\" \"+b.className+\" \").indexOf(\" mousetrap \")||D(b,this.target))return!1;if(\"composedPath\"in a&&\"function\"===typeof a.composedPath){var c=a.composedPath()[0];c!==a.target&&(b=c)}return\"INPUT\"==b.tagName||\"SELECT\"==b.tagName||\"TEXTAREA\"==b.tagName||b.isContentEditable};d.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};d.addKeycodes=function(a){for(var b in a)a.hasOwnProperty(b)&&(n[b]=a[b]);p=null};\r\nd.init=function(){var a=d(u),b;for(b in a)\"_\"!==b.charAt(0)&&(d[b]=function(b){return function(){return a[b].apply(a,arguments)}}(b))};d.init();\"undefined\"!==typeof module&&module.exports&&(module.exports=d);\"function\"===typeof define&&define.amd&&define(function(){return d})}})(\"undefined\"!==typeof window?window:null,\"undefined\"!==typeof window?document:null);\n"
},
"lib/mp3-worker": {
"content": "var remoteWorker;\n\nmodule.exports = function(samples) {\n return remoteWorker(\"lame-worker.js\").then(function(worker) {\n return new Promise(function(resolve, reject) {\n worker.postMessage(samples);\n return worker.onmessage = function(e) {\n return resolve(e.data);\n };\n });\n });\n};\n\nremoteWorker = function(url) {\n return fetch(url).then(function(response) {\n return response.arrayBuffer();\n }).then(function(buffer) {\n var blob;\n blob = new Blob([buffer], {\n type: \"application/javascript\"\n });\n return URL.createObjectURL(blob);\n }).then(function(url) {\n return new Worker(url);\n });\n};\n"
},
"lib/pattern": {
"content": "\n/*\nPattern\n\nHolds a pattern of notes. Used as 'stamps', a person can copy a\nselection of notes and paint them as a group onto the staff.\n\nStatus: Needs review\n */\nmodule.exports = function(I, self) {\n if (I == null) {\n I = {};\n }\n if (self == null) {\n self = Model(I);\n }\n defaults(I, {\n notes: []\n });\n self.attrAccessor(\"notes\");\n return self;\n};\n"
},
"lib/read-file": {
"content": "module.exports = function(_arg) {\n var accept, change, input;\n accept = _arg.accept, change = _arg.change;\n if (accept == null) {\n accept = \"\";\n }\n input = document.createElement('input');\n input.type = \"file\";\n input.setAttribute(\"accept\", accept);\n input.onchange = function() {\n var file, reader;\n reader = new FileReader();\n file = input.files[0];\n if (file) {\n change(file);\n }\n return input.value = null;\n };\n return input;\n};\n"
},
"lib/section": {
"content": "\n/*\nSection\n=======\n\nA `Section` is a list of [beat, staffNote, accidental, instrument] tuples.\n\nIt includes a length in beats and a tempo in bpm.\n */\nvar inRange, max, min, quantize, _ref;\n\n_ref = require(\"./util\"), inRange = _ref.inRange, quantize = _ref.quantize;\n\nrequire(\"./extensions\");\n\nmin = Math.min, max = Math.max;\n\nmodule.exports = function(I, self) {\n if (I == null) {\n I = {};\n }\n if (self == null) {\n self = Model(I);\n }\n defaults(I, {\n keySignature: 0,\n length: 64,\n name: \"Untitled Section\",\n notes: [],\n presetName: \"purple\",\n tempo: 90,\n timeSignature: \"4/4\"\n });\n I.keySignature = I.keySignature | 0;\n I.length = I.length | 0;\n self.attrObservable(\"keySignature\", \"name\", \"tempo\", \"notes\", \"presetName\", \"timeSignature\");\n self.observable(\"length\", function(value) {\n var v;\n v = parseFloat(value) || 0;\n return min(max(1, v), 9999);\n });\n return self.extend({\n addNote: function(note) {\n return self.notes.push(note);\n },\n removeNoteByReference: function(note) {\n var index;\n index = self.notes.indexOf(note);\n if (index >= 0) {\n return self.notes.splice(index, 1);\n }\n },\n removeNote: function(_arg, nearby) {\n var at, matched, matches, note, time, within, _ref1;\n time = _arg[0], note = _arg[1];\n _ref1 = self.notes.filter(function(_arg1) {\n var n, t;\n t = _arg1[0], n = _arg1[1];\n return note === n && time === t;\n }), matched = _ref1[_ref1.length - 1];\n if (matched) {\n return self.notes.splice(self.notes.indexOf(matched), 1);\n }\n if (nearby) {\n at = nearby.at, within = nearby.within;\n matches = self.notes.filter(function(_arg1) {\n var n, t, _ref2;\n t = _arg1[0], n = _arg1[1];\n return note === n && ((-within < (_ref2 = t - at) && _ref2 < within));\n });\n if (matches.length) {\n matches.sort(function(a, b) {\n return Math.abs(a[0] - at) - Math.abs(b[0] - at);\n });\n matched = matches[0];\n return self.notes.splice(self.notes.indexOf(matched), 1);\n }\n }\n },\n deleteRange: function(range) {\n return self.notes(self.notes.filter(function(beatNote) {\n return !inRange(range, beatNote);\n }));\n },\n duration: function() {\n return self.length() * 60 / self.tempo();\n },\n nonEmptyDuration: function() {\n var lastBeatTime, quantizedLastBeat, t;\n lastBeatTime = self.notes.reduce(function(lastBeat, note) {\n return Math.max(lastBeat, note[0]);\n }, 0);\n quantizedLastBeat = quantize(lastBeatTime, 4);\n if (quantizedLastBeat <= lastBeatTime) {\n t = quantizedLastBeat + 4;\n } else {\n t = quantizedLastBeat;\n }\n return t * 60 / self.tempo();\n },\n numerator: function() {\n return parseInt(self.timeSignature()) || 4;\n },\n retempo: function(s) {\n self.tempo(self.tempo() * s);\n self.notes.forEach(function(n) {\n return n[0] *= s;\n });\n self.length(self.length() * s);\n return self;\n },\n upcomingNotes: function(t, dt, f) {\n if (t + dt < 0) {\n return;\n }\n if (t >= I.length) {\n return;\n }\n return self.notes.forEach(function(note) {\n var beat;\n beat = note[0];\n if ((t <= beat && beat < t + dt)) {\n return f(note);\n }\n });\n }\n });\n};\n"
},
"lib/stripe-checkout": {
"content": "var CHECKOUT_API, Modal, checkoutId, productId, result, stripe;\n\ntry {\n stripe = Stripe('pk_PPCRZgLrovwFHSKmMkjtVONHDs3pR');\n} catch (_error) {}\n\nCHECKOUT_API = \"https://api.danielx.net\";\n\nproductId = 1;\n\nModal = system.ui.Modal;\n\nresult = window.location.search.match(/^\\?checkout_session_id=(.*)/);\n\nif (result) {\n checkoutId = result[1];\n Modal.alert(\"Thank you for your purchase, check your email for the download link!\");\n}\n\nmodule.exports = {\n purchase: function() {\n return fetch(\"\" + CHECKOUT_API + \"/purchases/\" + productId).then(function(r) {\n return r.json();\n }).then(function(data) {\n console.log(data);\n return data.id;\n }).then(function(sessionId) {\n return stripe.redirectToCheckout({\n sessionId: sessionId\n }).then(function(result) {\n if (result.error != null) {\n return console.error(result.error);\n }\n });\n })[\"catch\"](function() {\n return Modal.alert(\"I AM ERROR\");\n });\n }\n};\n"
},
"lib/stripe-elements": {
"content": "var card, elements, form, stripe, stripeTokenHandler, style;\n\nstripe = Stripe('pk_znR9dUa0sPXSlVv2009vpWdtexnnq');\n\nelements = stripe.elements();\n\nstyle = {\n base: {\n color: '#32325d',\n fontFamily: '\"Helvetica Neue\", Helvetica, sans-serif',\n fontSmoothing: 'antialiased',\n fontSize: '16px',\n '::placeholder': {\n color: '#aab7c4'\n }\n },\n invalid: {\n color: '#fa755a',\n iconColor: '#fa755a'\n }\n};\n\ncard = elements.create('card', {\n style: style\n});\n\ncard.mount('#card-element');\n\ncard.addEventListener('change', function(event) {\n var displayError;\n displayError = document.getElementById('card-errors');\n if (event.error) {\n return displayError.textContent = event.error.message;\n } else {\n return displayError.textContent = '';\n }\n});\n\nform = document.getElementById('payment-form');\n\nform.addEventListener('submit', function(event) {\n event.preventDefault();\n return stripe.createToken(card).then(function(result) {\n var errorElement;\n if (result.error) {\n errorElement = document.getElementById('card-errors');\n return errorElement.textContent = result.error.message;\n } else {\n return stripeTokenHandler(result.token);\n }\n });\n});\n\nstripeTokenHandler = function(token) {\n return form.submit();\n};\n"
},
"lib/test/audio-context": {
"content": "\n"
},
"lib/test/audio-encoder": {
"content": "\n"
},
"lib/test/briefcase": {
"content": "\n"
},
"lib/test/channel": {
"content": "\n"
},
"lib/test/clipboard-copy": {
"content": "\n"
},
"lib/test/drop": {
"content": "\n"
},
"lib/test/encoder/index": {
"content": "var Encoder, abs, audioBufferToMP3, audioBufferToWav, audioChannelDataToInt16, buffer, c, context, downloadLink, fnToWorker, i, monoBuffer, numberOfChannels, stereoBuffer, _ref;\n\n_ref = Encoder = require(\"/lib/encoder/index\"), audioBufferToMP3 = _ref.audioBufferToMP3, audioBufferToWav = _ref.audioBufferToWav, audioChannelDataToInt16 = _ref.audioChannelDataToInt16, fnToWorker = _ref.fnToWorker;\n\nabs = Math.abs;\n\ncontext = new AudioContext();\n\nstereoBuffer = context.createBuffer(2, 65536, 44100);\n\nmonoBuffer = context.createBuffer(1, 65536, 44100);\n\nnumberOfChannels = stereoBuffer.numberOfChannels;\n\nc = 0;\n\nwhile (c < numberOfChannels) {\n buffer = stereoBuffer.getChannelData(c);\n i = 0;\n while (i < buffer.length) {\n buffer[i] = Math.random() * 2 - 1;\n i++;\n }\n c++;\n}\n\nbuffer = monoBuffer.getChannelData(0);\n\ni = 0;\n\nwhile (i < buffer.length) {\n buffer[i] = Math.random() * 2 - 1;\n i++;\n}\n\ndownloadLink = function(blob, name) {\n var link;\n link = document.createElement('a');\n link.textContent = link.download = name;\n link.href = URL.createObjectURL(blob);\n document.body.appendChild(link);\n};\n\ndescribe(\"Audio Encoder\", function() {\n it(\"should encode MP3s in stereo\", function() {\n return audioBufferToMP3(stereoBuffer);\n });\n it(\"should encode MP3s in mono\", function() {\n return audioBufferToMP3(monoBuffer);\n });\n it(\"should encode wav in stereo\", function() {\n return audioBufferToWav(stereoBuffer);\n });\n it(\"should encode wav in mono\", function() {\n return audioBufferToWav(monoBuffer);\n });\n it(\"should convert audioBuffer to Int16Array\", function() {\n assert(audioChannelDataToInt16(monoBuffer.getChannelData(0)));\n assert(audioChannelDataToInt16(stereoBuffer.getChannelData(0)));\n return assert(audioChannelDataToInt16(stereoBuffer.getChannelData(1)));\n });\n it(\"should convert to Int16Array a web worker\", function() {\n var workerFn;\n workerFn = fnToWorker(audioChannelDataToInt16);\n return workerFn(monoBuffer.getChannelData(0));\n });\n it(\"should have a proper distribution of values when converting to int16\");\n return function() {\n var actual, b, bins, d, expected, l, maxDiscrepancy, maxDiscrepancyIndex, sum, v;\n b = audioChannelDataToInt16(monoBuffer.getChannelData(0));\n bins = new Map;\n l = b.length;\n i = 0;\n while (i < l) {\n v = b[i] + 0x8000;\n c = bins.get(v) | 0;\n bins.set(v, c + 1);\n i++;\n }\n console.log(\"i\", i);\n expected = 0;\n actual = 0;\n maxDiscrepancy = 0;\n maxDiscrepancyIndex = null;\n i = 0;\n while (i < 65536) {\n c = bins.get(i) | 0;\n expected += 10;\n actual += c;\n d = abs(expected - actual);\n if (d > maxDiscrepancy) {\n maxDiscrepancy = d;\n maxDiscrepancyIndex = i;\n }\n i++;\n }\n sum = Array.from(bins.values()).reduce(function(a, b) {\n return a + b;\n });\n console.log(\"SUM\", sum, actual, expected);\n console.log(b, bins);\n return console.log(\"Disc\", maxDiscrepancy, maxDiscrepancyIndex);\n };\n});\n"
},
"lib/test/encoder/wave-worker": {
"content": "\n"
},
"lib/test/extensions": {
"content": "\n"
},
"lib/test/gist": {
"content": "\n"
},
"lib/test/hotkeys": {
"content": "\n"
},
"lib/test/legacy/channel": {
"content": "\n"
},
"lib/test/legacy/gist": {
"content": "var Gist;\n\nGist = require(\"/lib/legacy/gist\");\n\ndescribe(\"Gist\", function() {\n return it(\"should load gists\", function() {\n return Gist.load(\"fec8cabc6433099dbe22\");\n });\n});\n"
},
"lib/test/legacy/pattern": {
"content": "\n"
},
"lib/test/legacy/song": {
"content": "var Pattern, Song;\n\nSong = require(\"/lib/legacy/song\");\n\nPattern = require(\"/lib/legacy/pattern\");\n\ndescribe(\"Song\", function() {\n it(\"Should know its size\", function() {\n var song;\n song = Song({\n patterns: [\n {\n beats: 4\n }\n ]\n });\n return assert.equal(song.size(), 4, \"song.size() is \" + (song.size()));\n });\n it(\"Should know the correct size when the pattern has a different size\", function() {\n var song;\n song = Song({\n patterns: [\n {\n beats: 10\n }\n ]\n });\n return assert.equal(song.size(), 10);\n });\n it(\"Should have ten patterns\", function() {\n var song;\n song = Song();\n return assert.equal(song.toJSON().patterns.length, 10);\n });\n it(\"Should know it's tempo\", function() {\n var song;\n song = Song({\n tempo: 54\n });\n return assert.equal(song.tempo(), 54);\n });\n it(\"Should return upcoming notes\", function() {\n var song;\n song = Song();\n return assert.equal(song.upcomingNotes(0, 1).length, 0);\n });\n describe(\"With a single pattern\", function() {\n var pattern, song;\n pattern = Pattern({\n notes: [0, 1, 2, 3].map(function(i) {\n return [i, 0, 0];\n })\n });\n song = Song({\n channels: [\n {\n data: {\n 0: 0\n }\n }\n ],\n patterns: [pattern.I]\n });\n it(\"should return all the notes\", function() {\n assert.equal(song.upcomingNotes(0, 1).length, 1);\n assert.equal(song.upcomingNotes(1, 1).length, 1);\n assert.equal(song.upcomingNotes(2, 1).length, 1);\n assert.equal(song.upcomingNotes(3, 1).length, 1);\n return assert.equal(song.upcomingNotes(0, 4).length, 4);\n });\n return it(\"shouldn't loop past the end\", function() {\n assert.equal(song.upcomingNotes(4, 1).length, 0);\n return assert.equal(song.upcomingNotes(0, 8).length, 4);\n });\n });\n describe(\"With multiple patterns in parallel\", function() {\n var pattern1, pattern2, song;\n pattern1 = Pattern({\n notes: [0, 1, 2, 3].map(function(i) {\n return [i, 0, 0];\n })\n });\n pattern2 = Pattern({\n notes: [0, 1, 2, 3].map(function(i) {\n return [i + 0.5, 0, 1];\n })\n });\n song = Song({\n channels: [\n {\n data: {\n 0: 0\n }\n }, {\n data: {\n 0: 1\n }\n }\n ],\n patterns: [pattern1.I, pattern2.I]\n });\n it(\"should return all the notes\", function() {\n assert.equal(song.upcomingNotes(0, 1).length, 2);\n assert.equal(song.upcomingNotes(1, 1).length, 2);\n assert.equal(song.upcomingNotes(2, 1).length, 2);\n assert.equal(song.upcomingNotes(3, 1).length, 2);\n return assert.equal(song.upcomingNotes(0, 4).length, 8);\n });\n return it(\"shouldn't loop past the end\", function() {\n assert.equal(song.upcomingNotes(4, 1).length, 0);\n return assert.equal(song.upcomingNotes(3, 2).length, 2);\n });\n });\n describe(\"With two patterns in sequence\", function() {\n var pattern1, song;\n pattern1 = Pattern({\n notes: [0, 1, 2, 3].map(function(i) {\n return [i, 0, 0];\n })\n });\n song = Song({\n channels: [\n {\n data: {\n 0: 0,\n 4: 0\n }\n }\n ],\n patterns: [pattern1.I]\n });\n it(\"should return all the notes in short timesteps\", function() {\n assert.equal(song.upcomingNotes(0, 1).length, 1);\n assert.equal(song.upcomingNotes(1, 1).length, 1);\n assert.equal(song.upcomingNotes(2, 1).length, 1);\n assert.equal(song.upcomingNotes(3, 1).length, 1);\n assert.equal(song.upcomingNotes(4, 1).length, 1);\n assert.equal(song.upcomingNotes(5, 1).length, 1);\n assert.equal(song.upcomingNotes(6, 1).length, 1);\n return assert.equal(song.upcomingNotes(7, 1).length, 1);\n });\n it(\"should Work with larger timesteps\", function() {\n assert.equal(song.upcomingNotes(0, 4).length, 4);\n return assert.equal(song.upcomingNotes(4, 4).length, 4);\n });\n it(\"should cross pattern boundries\", function() {\n return assert.equal(song.upcomingNotes(0, 8).length, 8);\n });\n it(\"shouldn't loop past the end\", function() {\n assert.equal(song.upcomingNotes(8, 1).length, 0);\n return assert.equal(song.upcomingNotes(0, 16).length, 8);\n });\n return it(\"should have the correct times for the later notes\", function() {\n var notes;\n notes = song.upcomingNotes(0, 16);\n assert.equal(notes[4][0], 4);\n notes = song.upcomingNotes(3, 16);\n return assert.equal(notes[1][0], 1);\n });\n });\n describe(\"#canSet\", function() {\n return it(\"should allow placing a pattern if there is space, not if not\", function() {\n var song;\n song = Song({\n patterns: [\n {\n beats: 8\n }\n ],\n channels: [\n {\n data: {\n 0: 0,\n 8: 0\n }\n }\n ]\n });\n assert(song.canSet(0, 16, 0));\n return assert(!song.canSet(0, 15, 0));\n });\n });\n return describe(\"#patternsDataAt\", function() {\n return it(\"should get a pattern from each channel\", function() {\n var song;\n song = Song({\n patterns: [\n {\n beats: 8\n }, {\n beats: 8\n }\n ],\n channels: [\n {\n data: {\n 0: 0\n }\n }, {\n data: {\n 7: 0\n }\n }\n ]\n });\n assert.equal(song.patternsDataAt(0).filter(function(x) {\n return x;\n }).length, 1);\n assert.equal(song.patternsDataAt(7).filter(function(x) {\n return x;\n }).length, 2);\n return assert.equal(song.patternsDataAt(8).filter(function(x) {\n return x;\n }).length, 1);\n });\n });\n});\n"
},
"lib/test/midi-input": {
"content": "\n"
},
"lib/test/midi": {
"content": "\n"
},
"lib/test/model": {
"content": "\n"
},
"lib/test/mousetrap": {
"content": "\n"
},
"lib/test/mp3-worker": {
"content": "\n"
},
"lib/test/pattern": {
"content": "\n"
},
"lib/test/pcm-worker": {
"content": "describe(\"PCM worker\", function() {\n return it(\"should convert audio buffer to Int16Array\", function() {\n var audioBuffer, buffer, c, context, i, numberOfChannels;\n context = new AudioContext();\n audioBuffer = context.createBuffer(2, 22050, 44100);\n numberOfChannels = audioBuffer.numberOfChannels;\n c = 0;\n while (c < numberOfChannels) {\n buffer = audioBuffer.getChannelData(c);\n i = 0;\n while (i < buffer.length) {\n buffer[i] = Math.random() * 2 - 1;\n i++;\n }\n c++;\n }\n return new Promise(function(resolve, reject) {\n var url, worker, workerSource;\n workerSource = new Blob([PACKAGE.distribution[\"lib/pcm-worker\"].content], {\n type: \"application/javascript\"\n });\n url = URL.createObjectURL(workerSource);\n worker = new Worker(url);\n worker.onmessage = function(e) {\n URL.revokeObjectURL(url);\n return resolve(e.data);\n };\n return worker.postMessage(audioBuffer.getChannelData(0));\n }).then(function(response) {\n assert(response instanceof Int16Array);\n return console.log(response);\n });\n });\n});\n"
},
"lib/test/read-file": {
"content": "\n"
},
"lib/test/s3-fs": {
"content": "\n"
},
"lib/test/section": {
"content": "var Section;\n\nSection = require(\"../section\");\n\ndescribe(\"Section\", function() {\n it(\"should return notes within a timeframe\");\n it(\"should know its duration\", function() {\n var section;\n section = Section({\n length: 120,\n tempo: 60\n });\n return assert.equal(section.duration(), 120);\n });\n return it(\"should know the numerator of its time signature\", function() {\n var section;\n section = Section({\n length: 120,\n tempo: 60,\n timeSignature: \"17/9\"\n });\n return assert.equal(section.numerator(), 17);\n });\n});\n"
},
"lib/test/stripe-checkout": {
"content": "\n"
},
"lib/test/stripe-elements": {
"content": "\n"
},
"lib/test/stripe": {
"content": "\n"
},
"lib/test/undo": {
"content": "var Undo;\n\nUndo = require(\"../undo\");\n\ndescribe(\"Undo\", function() {\n it(\"should track state\", function() {\n var sA, sB, sC, sD, sE, u;\n u = Undo({\n restoreState: function(s) {\n return this._state = s;\n }\n });\n sA = {};\n sB = {};\n sC = {};\n assert.equal(u.undoDisabled(), true);\n u.pushState(sA);\n assert.equal(u.undoDisabled(), true);\n u.pushState(sB);\n assert.equal(u.undoDisabled(), false);\n u.pushState(sC);\n assert.equal(u.undoDisabled(), false);\n u.undo();\n assert.equal(u._state, sB);\n u.undo();\n assert.equal(u._state, sA);\n u.undo();\n assert.equal(u._state, sA);\n u.redo();\n assert.equal(u._state, sB);\n u.redo();\n assert.equal(u._state, sC);\n u.undo();\n u.undo();\n sD = {};\n sE = {};\n u.pushState(sD);\n assert.equal(u._state, sA);\n assert.equal(u.redoDisabled(), true);\n u.pushState(sE);\n assert.equal(u.redoDisabled(), true);\n u.undo();\n assert.equal(u._state, sD);\n u.redo();\n return assert.equal(u._state, sE);\n });\n return it(\"should have a max size\", function() {\n var states, u, _i, _results;\n u = Undo({\n restoreState: function(s) {\n return this._state = s;\n }\n });\n states = (function() {\n _results = [];\n for (_i = 0; _i < 25; _i++){ _results.push(_i); }\n return _results;\n }).apply(this).map(function(i) {\n return {\n n: i\n };\n });\n states.forEach(function(s) {\n return u.pushState(s);\n });\n assert.equal(u.redoDisabled(), true);\n assert.equal(u.undoDisabled(), false);\n [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19].forEach(function() {\n return u.undo();\n });\n assert.equal(u.undoDisabled(), true);\n return assert.equal(u._state, states[4]);\n });\n});\n"
},
"lib/test/util": {
"content": "var emptyElement, pitchToStaffNote, quantize, staffNoteToPitch, _ref;\n\n_ref = require(\"../util\"), emptyElement = _ref.emptyElement, quantize = _ref.quantize, staffNoteToPitch = _ref.staffNoteToPitch, pitchToStaffNote = _ref.pitchToStaffNote;\n\ndescribe(\"Util\", function() {\n it(\"should quantize\", function() {\n assert.equal(quantize(61, 4), 60);\n assert.equal(quantize(63, 4), 64);\n assert.equal(quantize(0.77, 0.25), 0.75);\n assert.equal(quantize(0.55, 0.25), 0.5);\n return assert.equal(quantize(0.33333, 0), 0.33333);\n });\n it(\"should empty elements\", function() {\n var d;\n d = document.createElement(\"div\");\n d.appendChild(document.createElement(\"a\"));\n d.appendChild(document.createElement(\"a\"));\n d.appendChild(document.createElement(\"a\"));\n d.appendChild(document.createElement(\"a\"));\n assert.equal(d.children.length, 4);\n emptyElement(d);\n return assert.equal(d.children.length, 0);\n });\n it(\"should give the pitch for a key signature\", function() {\n var p;\n p = staffNoteToPitch(0, 0, 0);\n assert.equal(p, 0);\n p = staffNoteToPitch(3, 0, 0);\n assert.equal(p, 5);\n p = staffNoteToPitch(3, 0, 1);\n return assert.equal(p, 6);\n });\n return it(\"should convert pitch to staff notes\", function() {\n return assert.equal(pitchToStaffNote(0)[0], 0);\n });\n});\n"
},
"lib/test/wave-worker": {
"content": "\n"
},
"lib/touch-canvas": {
"content": "var Bindable, Model, PixieCanvas, TouchCanvas, listen;\n\nBindable = system.ui.Bindable;\n\nModel = require(\"./model\");\n\nPixieCanvas = require(\"pixie-canvas\");\n\nTouchCanvas = function(I) {\n var active, element, localPosition, processTouches, self;\n if (I == null) {\n I = {};\n }\n self = PixieCanvas(I);\n Model(I, self);\n self.include(Bindable);\n element = self.element();\n active = false;\n listen(element, \"mousedown\", function(e) {\n active = true;\n return self.trigger(\"touch\", localPosition(e));\n });\n listen(element, \"touchstart\", function(e) {\n return processTouches(event, function(touch) {\n return self.trigger(\"touch\", localPosition(touch));\n });\n });\n listen(element, \"mousemove\", function(e) {\n if (active) {\n return self.trigger(\"move\", localPosition(e));\n }\n });\n listen(document, \"mousemove\", function(e) {\n if (active) {\n return self.trigger(\"move\", localPosition(e));\n }\n });\n listen(element, \"touchmove\", function(e) {\n return processTouches(event, function(touch) {\n return self.trigger(\"move\", localPosition(touch));\n });\n });\n listen(element, \"mouseup\", function(e) {\n self.trigger(\"release\", localPosition(e));\n active = false;\n });\n listen(element, \"touchend\", function(e) {\n return processTouches(event, function(touch) {\n return self.trigger(\"release\", localPosition(touch));\n });\n });\n listen(document, \"mouseup\", function(e) {\n if (active) {\n self.trigger(\"release\", localPosition(e));\n }\n active = false;\n });\n processTouches = function(event, fn) {\n var touches;\n event.preventDefault();\n if (event.type === \"touchend\") {\n touches = event.changedTouches;\n } else {\n touches = event.touches;\n }\n if (typeof self.debug === \"function\") {\n self.debug(Array.prototype.map.call(touches, function(_arg) {\n var identifier, pageX, pageY;\n identifier = _arg.identifier, pageX = _arg.pageX, pageY = _arg.pageY;\n return \"[\" + identifier + \": \" + pageX + \", \" + pageY + \" (\" + event.type + \")]\\n\";\n }));\n }\n return Array.prototype.forEach.call(touches, fn);\n };\n localPosition = function(e) {\n var point, rect;\n rect = element.getBoundingClientRect();\n point = {\n x: (e.pageX - rect.left) / rect.width,\n y: (e.pageY - rect.top) / rect.height\n };\n point.identifier = (e.identifier + 1) || 0;\n return point;\n };\n return self;\n};\n\nlisten = function(element, event, handler) {\n return element.addEventListener(event, handler, false);\n};\n\nmodule.exports = TouchCanvas;\n"
},
"lib/undo": {
"content": "\n/*\nProvides a basic undo stack storing opaque states. The consumer is\nexpected to provide a `restoreState` method which is invoked when undoing or\nredoing.\n\nThe consumer should call `pushState` to push states onto the undo stack.\n */\nvar Observable;\n\nObservable = system.ui.Observable;\n\nmodule.exports = function(self) {\n var maxSize, nextState, popState, pushState, redoDisabled, undoDisabled, undoIndex, undoStack;\n undoStack = [];\n undoIndex = Observable(-1);\n maxSize = 20;\n undoDisabled = function() {\n return undoIndex() <= 0;\n };\n redoDisabled = function() {\n return undoIndex() >= undoStack.length - 1;\n };\n popState = function() {\n if (!undoDisabled()) {\n return undoStack[undoIndex.decrement(1)];\n }\n };\n nextState = function() {\n if (!redoDisabled()) {\n return undoStack[undoIndex.increment(1)];\n }\n };\n pushState = function(state) {\n undoStack = undoStack.slice(0, undoIndex() + 1).slice(-maxSize);\n return undoIndex(undoStack.push(state) - 1);\n };\n return Object.assign(self, {\n undo: function() {\n var state;\n if (state = popState()) {\n return self.restoreState(state);\n }\n },\n redo: function() {\n var state;\n if (state = nextState()) {\n return self.restoreState(state);\n }\n },\n undoDisabled: undoDisabled,\n redoDisabled: redoDisabled,\n pushState: pushState,\n undoStack: function() {\n return undoStack;\n }\n });\n};\n"
},
"lib/util": {
"content": "\n/*\nUtil\n\nHolds a bunch of methods for note math, conversions, some dom stuff.\n\nStatus: Active\n */\nvar clamp, composedPath, inverseScale, mod, noteCompare, notes, quantize, scale, staffNoteToPitch, wrap;\n\nscale = {\n \"-7\": {\n 0: -1,\n 1: 1,\n 2: 3,\n 3: 4,\n 4: 6,\n 5: 8,\n 6: 10\n },\n \"-6\": {\n 0: -1,\n 1: 1,\n 2: 3,\n 3: 5,\n 4: 6,\n 5: 8,\n 6: 10\n },\n \"-5\": {\n 0: 0,\n 1: 1,\n 2: 3,\n 3: 5,\n 4: 6,\n 5: 8,\n 6: 10\n },\n \"-4\": {\n 0: 0,\n 1: 1,\n 2: 3,\n 3: 5,\n 4: 7,\n 5: 8,\n 6: 10\n },\n \"-3\": {\n 0: 0,\n 1: 2,\n 2: 3,\n 3: 5,\n 4: 7,\n 5: 8,\n 6: 10\n },\n \"-2\": {\n 0: 0,\n 1: 2,\n 2: 3,\n 3: 5,\n 4: 7,\n 5: 9,\n 6: 10\n },\n \"-1\": {\n 0: 0,\n 1: 2,\n 2: 4,\n 3: 5,\n 4: 7,\n 5: 9,\n 6: 10\n },\n 0: {\n 0: 0,\n 1: 2,\n 2: 4,\n 3: 5,\n 4: 7,\n 5: 9,\n 6: 11\n },\n 1: {\n 0: 0,\n 1: 2,\n 2: 4,\n 3: 6,\n 4: 7,\n 5: 9,\n 6: 11\n },\n 2: {\n 0: 1,\n 1: 2,\n 2: 4,\n 3: 6,\n 4: 7,\n 5: 9,\n 6: 11\n },\n 3: {\n 0: 1,\n 1: 2,\n 2: 4,\n 3: 6,\n 4: 8,\n 5: 9,\n 6: 11\n },\n 4: {\n 0: 1,\n 1: 3,\n 2: 4,\n 3: 6,\n 4: 8,\n 5: 9,\n 6: 11\n },\n 5: {\n 0: 1,\n 1: 3,\n 2: 4,\n 3: 6,\n 4: 8,\n 5: 10,\n 6: 11\n },\n 6: {\n 0: 1,\n 1: 3,\n 2: 5,\n 3: 6,\n 4: 8,\n 5: 10,\n 6: 11\n },\n 7: {\n 0: 1,\n 1: 3,\n 2: 5,\n 3: 6,\n 4: 8,\n 5: 10,\n 6: 12\n }\n};\n\ninverseScale = Object.keys(scale[0]).reduce(function(m, key) {\n var value;\n value = scale[0][key];\n m[value] = key | 0;\n return m;\n}, {});\n\nnotes = \"C C# D D# E F F# G G# A A# B\".split(\" \");\n\nwrap = function(array, index) {\n return array[mod(index, array.length)];\n};\n\nmod = function(n, k) {\n return (n % k + k) % k;\n};\n\n\n/*\nQuantize a number, rounding it to the nearest nth unit.\n\n assert.equal quantize(61, 4), 60\n assert.equal quantize(63, 4), 64\n\n assert.equal quantize(0.77, 0.25), 0.75\n assert.equal quantize(0.55, 0.25), 0.5\n\nIf the unit is zero then return the number as is.\n\n assert.equal quantize(0.33333, 0), 0.33333\n */\n\nquantize = function(x, n) {\n if (n === 0) {\n return x;\n }\n return ((x / n + 1 / 2) | 0) * n;\n};\n\nnoteCompare = function(_arg, _arg1) {\n var na, nb, ta, tb;\n ta = _arg[0], na = _arg[1];\n tb = _arg1[0], nb = _arg1[1];\n if (ta < tb) {\n return -1;\n } else if (ta > tb) {\n return 1;\n } else {\n if (na < nb) {\n return -1;\n } else if (na > nb) {\n return 1;\n } else {\n return 0;\n }\n }\n};\n\nclamp = function(x, min, max) {\n return Math.min(max, Math.max(min, x));\n};\n\ncomposedPath = function(el) {\n var path;\n path = [];\n while (el) {\n path.push(el);\n if (el.tagName === 'HTML') {\n path.push(document);\n path.push(window);\n break;\n }\n el = el.parentElement;\n }\n return path;\n};\n\nstaffNoteToPitch = function(n, a, key) {\n var o, r;\n if (a == null) {\n a = 0;\n }\n if (key == null) {\n key = 0;\n }\n r = mod(n, 7);\n o = Math.floor(n / 7);\n return o * 12 + scale[key][r] + a;\n};\n\nmodule.exports = {\n accidentalToSymbol: function(accidental) {\n if (accidental === 1) {\n return \"♯\";\n } else if (accidental === -1) {\n return \"♭\";\n } else {\n return \"\";\n }\n },\n composedPath: composedPath,\n clamp: clamp,\n mod: mod,\n noteCompare: noteCompare,\n quantize: quantize,\n emptyElement: function(el) {\n var _results;\n _results = [];\n while (el.lastChild) {\n _results.push(el.lastChild.remove());\n }\n return _results;\n },\n inRange: function(range, _arg) {\n var n, n0, n1, t, t0, t1, _ref, _ref1;\n t = _arg[0], n = _arg[1];\n (_ref = range[0], t0 = _ref[0], t1 = _ref[1]), (_ref1 = range[1], n0 = _ref1[0], n1 = _ref1[1]);\n return (t0 <= t && t <= t1) && (n0 <= n && n <= n1);\n },\n\n /*\n noteName\n \n Translate a number to a music note name.\n \n -1 is B3\n 0 is C4\n 1 is C#4\n ...\n */\n noteName: function(noteNumber) {\n var note, octave;\n noteNumber |= 0;\n note = wrap(notes, noteNumber);\n octave = 4 + (noteNumber / 12) | 0;\n return \"\" + note + octave;\n },\n patternSampleNotes: function(pattern, offset) {\n if (offset == null) {\n offset = 0;\n }\n return pattern.notes().reduce(function(notes, _arg) {\n var accidental, instrument, instrumentPitchExists, pitch, staffNote, _;\n _ = _arg[0], staffNote = _arg[1], accidental = _arg[2], instrument = _arg[3];\n if (notes.length < 4) {\n pitch = staffNoteToPitch(staffNote + offset, accidental);\n instrumentPitchExists = notes.some(function(_arg1) {\n var i, p;\n i = _arg1[0], p = _arg1[1];\n return i === instrument && p === pitch;\n });\n if (!instrumentPitchExists) {\n notes.push([instrument, pitch]);\n }\n }\n return notes;\n }, []);\n },\n pitchToStaffNote: function(n) {\n var a, o, r, staffNote;\n r = mod(n, 12);\n o = Math.floor(n / 12);\n staffNote = inverseScale[r];\n if (staffNote != null) {\n a = 0;\n } else {\n staffNote = inverseScale[r - 1];\n a = 1;\n }\n return [o * 7 + staffNote, a];\n },\n staffNoteToPitch: staffNoteToPitch,\n wrap: wrap\n};\n"
},
"main": {
"content": "var ButtonTemplate, closing, e, img, remote, util;\n\nrequire(\"./lib/extensions\");\n\nglobal.player = require(\"./player\")();\nplayer.purchased(true)\nutil = system.util;\n\nutil.applyStyle(system.ui.Style.all, \"system\");\n\nutil.applyStyle(require(\"./style\"), \"app\");\n\ndocument.body.appendChild(player.element);\n\nArray.prototype.forEach.call(document.querySelectorAll('canvas'), function(element) {\n return element.oncontextmenu = function(e) {\n return e.preventDefault();\n };\n});\n\nimg = new Image;\n\nimg.src = \"assets/box-art.jpg\";\n\ntry {\n if (location.host && !global.ELECTRON_VERSION) {\n navigator.serviceWorker.register('./sw.js');\n }\n} catch (_error) {\n e = _error;\n console.warn(e.message);\n}\n\nsetInterval(function() {\n if (!player.purchased()) {\n return player.purchase();\n }\n}, 1000 * 60 * 15);\n\nif (global.ELECTRON_VERSION) {\n ButtonTemplate = require(\"./templates/button\");\n remote = global.require(\"electron\").remote;\n player.purchased(true);\n player.removeUnloadHandler();\n closing = false;\n window.addEventListener(\"beforeunload\", function(e) {\n if (closing) {\n return;\n }\n player.confirmUnsaved().then(function() {\n closing = true;\n return remote.getCurrentWindow().close();\n });\n return e.returnValue = true;\n });\n player.element.querySelector('section.buttons').appendChild(ButtonTemplate({\n text: \"🚪 Exit\",\n \"class\": \"exit\",\n click: function() {\n return remote.getCurrentWindow().close();\n }\n }));\n}\n"
},
"persistence": {
"content": "var Drop, Gist, Midi2Composer, Modal, Progress, PublishTemplate, ReadFile, clipboardCopy, publishView, _ref;\n\nGist = require(\"./lib/legacy/gist\");\n\nDrop = require(\"./lib/drop\");\n\nReadFile = require(\"./lib/read-file\");\n\nMidi2Composer = require(\"./lib/midi2composer\");\n\nPublishTemplate = require(\"./templates/publish\");\n\nclipboardCopy = require(\"./lib/clipboard-copy\");\n\n_ref = system.ui, Modal = _ref.Modal, Progress = _ref.Progress;\n\npublishView = Progress({\n message: \"Publishing...\"\n});\n\nmodule.exports = function(I, self) {\n var fileInput, jsonFromFile, prompted, unloadHandler;\n if (I == null) {\n I = {};\n }\n defaults(I, {\n unsaved: false\n });\n self.attrAccessor(\"unsaved\");\n jsonFromFile = function(file) {\n if (file.type.match(/^audio\\/mid/)) {\n if (self.purchased()) {\n return file.readAsArrayBuffer().then(function(buffer) {\n return Midi2Composer(new Uint8Array(buffer));\n });\n } else {\n self.purchase();\n return Promise.reject(\"Only full version can import MIDI\");\n }\n } else {\n return file.readAsJSON();\n }\n };\n Drop(document.documentElement, function(files) {\n return Promise.all(Array.prototype.map.call(files, function(file) {\n return jsonFromFile(file)[\"catch\"](function() {\n return null;\n });\n })).then(function(readFiles) {\n var json;\n json = readFiles.filter(function(f) {\n return !!f;\n })[0];\n if (json) {\n return self.fromJSON(json);\n }\n });\n });\n fileInput = ReadFile({\n accept: \".dxc,.mid,audio/mid,.midi,audio/midi\",\n change: function(file) {\n return jsonFromFile(file).then(self.fromJSON).then(function() {\n return Modal.hide();\n })[\"catch\"](console.error);\n }\n });\n fileInput.style.display = \"none\";\n document.body.appendChild(fileInput);\n prompted = false;\n unloadHandler = function(e) {\n if (prompted) {\n return;\n }\n prompted = true;\n setTimeout(function() {\n return prompted = false;\n });\n if (self.unsaved()) {\n e.returnValue = \"Your changes haven't yet been saved. If you leave now you will lose your work.\";\n }\n return e.returnValue;\n };\n window.addEventListener(\"beforeunload\", unloadHandler);\n setTimeout(function() {\n var hash;\n if (hash = location.hash) {\n return self.loadFromSlug(hash.substr(1));\n }\n }, 0);\n return self.extend({\n saveAs: function() {\n return Modal.prompt(\"Name\").then(function(name) {\n var data;\n if (!name) {\n return;\n }\n data = self.song().toJSON();\n localStorage[\"songs_\" + name] = JSON.stringify(data);\n return self.unsaved(false);\n });\n },\n saveFile: function() {\n return Modal.prompt(\"Name\", \"untitled\", \"Save Song\").then(function(name) {\n var blob;\n if (!name) {\n return;\n }\n blob = new Blob([JSON.stringify(self.song().toJSON())], {\n type: \"application/json\"\n });\n blob.download(\"\" + name + \".dxc\");\n return self.unsaved(false);\n });\n },\n openFile: function() {\n return fileInput.click();\n },\n loadFromSlug: function(slug) {\n return Promise.resolve().then(function() {\n if (slug.match(/^api-/)) {\n return self.loadAPIData(slug.substr(4));\n } else if (slug) {\n return self.loadGist(slug);\n }\n });\n },\n loadURL: function(url) {\n return fetch(url).then(function(result) {\n return result.json();\n }).then(self.fromJSON);\n },\n loadFromURLString: function() {\n return Modal.prompt(\"Paste shareable URL here:\", \"https://danielx.net/composer/#api-4IuXACKoqtSke9jTAThC5I_eWc4me53ze-1cri-L8kE\", \"Load from URL\").then(function(url) {\n var slug, _, _ref1;\n if (!url) {\n return;\n }\n if (url.indexOf(\"#\")) {\n _ref1 = url.split(\"#\"), _ = _ref1[0], slug = _ref1[1];\n return self.loadFromSlug(slug);\n } else {\n return self.loadURL(url);\n }\n });\n },\n loadDemo: function() {\n return self.confirmUnsaved().then(function() {\n Modal.hide();\n return self.fromJSON(require(\"./data/demo\"));\n });\n },\n loadLocalStorage: function() {\n return Modal.prompt(\"Name\").then(function(name) {\n if (!name) {\n return;\n }\n if (name) {\n return self.fromJSON(JSON.parse(localStorage[\"songs_\" + name]));\n }\n });\n },\n getState: function() {\n return JSON.parse(JSON.stringify(self.song().toJSON()));\n },\n restoreState: function(data) {\n self.song().fromJSON(JSON.parse(JSON.stringify(data)));\n return self.rerenderNotes();\n },\n confirmUnsaved: function() {\n return Promise.resolve().then(function() {\n if (self.unsaved()) {\n return Modal.confirm(\"If you continue your changes will be lost!\", \"Unsaved changes!\").then(function(confirmed) {\n if (!confirmed) {\n return Promise.reject();\n }\n });\n }\n });\n },\n fromJSON: function(data) {\n return self.confirmUnsaved().then(function() {\n return self._fromJSON(data);\n });\n },\n _fromJSON: function(data) {\n self.song().fromJSON(data);\n self.reset();\n return self.rerenderNotes();\n },\n removeUnloadHandler: function() {\n return window.removeEventListener(\"beforeunload\", unloadHandler);\n },\n publish: function() {\n Modal.show(publishView.element, {\n cancellable: false\n });\n return fetch(\"https://api.danielx.net/composer\", {\n method: 'POST',\n body: JSON.stringify(self.song()),\n headers: {\n 'Content-Type': 'text/plain'\n }\n }).then(function(res) {\n return res.json();\n }).then(function(id) {\n var e, url;\n url = \"https://danielx.net/composer/#api-\" + id;\n try {\n history.replaceState({}, \"\", url);\n } catch (_error) {\n e = _error;\n console.warn(e);\n }\n return Modal.show(PublishTemplate({\n value: url,\n status: Observable(\"\"),\n copy: function() {\n clipboardCopy(url);\n return this.status(\"Copied!\");\n },\n done: function() {\n return Modal.hide();\n }\n }), {\n cancellable: false\n });\n })[\"catch\"](function(e) {\n console.warn(e);\n return Modal.hide();\n });\n },\n loadAPIData: function(id) {\n return fetch(\"https://api.danielx.net/composer/data/\" + id, {\n method: 'GET',\n headers: {\n 'Content-Type': 'text/plain'\n }\n }).then(function(res) {\n return res.json();\n }).then(function(data) {\n self.fromJSON(data);\n return location.hash = \"api-\" + id;\n });\n },\n loadGist: function(id) {\n return Gist.load(id).then(function(data) {\n self.fromJSON(data);\n return location.hash = id;\n }, function() {\n return Modal.alert(\"Couldn't load gist with id: \" + id);\n });\n },\n loadGistPrompt: function() {\n var id;\n if (id = prompt(\"Gist id\", location.hash.substr(1) || \"0b4c4656a6eb1d246829\")) {\n return self.loadGist(id);\n }\n }\n });\n};\n"
},
"pixie": {
"content": "module.exports = {\n \"title\": \"Mario Paint Music Composer - danielx.net\",\n \"description\": \"This Mario Paint inspired composer tool is easy and fun. You can create simple\\nand beautiful songs right in your browser and share them with the world!\",\n \"iconURL\": \"https://danielx.net/composer/images/raccoon.png\",\n \"version\": \"0.4.7\",\n \"publishPath\": \"/My Briefcase/public/danielx.net/\",\n \"name\": \"composer\",\n \"dependencies\": {\n \"!system\": \"https://danielx.net/system/0.5.0-pre.4.json\"\n },\n \"remoteDependencies\": [\n \"https://js.stripe.com/v3/\"\n ],\n \"width\": 1024,\n \"height\": 960,\n \"sandbox\": \"allow-same-origin allow-modals allow-forms allow-pointer-lock allow-popups allow-scripts allow-popups-to-escape-sandbox midi\",\n \"cognito\": {\n \"identityPoolId\": \"us-east-1:4fe22da5-bb5e-4a78-a260-74ae0a140bf9\",\n \"poolData\": {\n \"UserPoolId\": \"us-east-1_cfvrlBLXG\",\n \"ClientId\": \"3fd84r6idec9iork4e9l43mp61\"\n },\n \"bucket\": \"whimsy-fs\"\n },\n \"manifest\": {\n \"name\": \"DanielX.net Paint Composer\",\n \"short_name\": \"Composer\",\n \"display\": \"standalone\",\n \"start_url\": \".\",\n \"scope\": \"./\",\n \"icons\": [\n {\n \"src\": \"https://danielx.net/composer/images/raccoon.png\",\n \"sizes\": \"48x48\"\n },\n {\n \"src\": \"https://danielx.net/composer/icon256.png\",\n \"sizes\": \"256x256\"\n }\n ]\n }};"
},
"player-audio": {
"content": "\n/*\nPlayer Audio\n============\n\nMain audio loop\n\nNeeds tempo, playable, start beat, end beat, looping mode to play.\n\nProvides playTime and playing methods.\n */\nvar Encoder, FX, FXNetwork, Modal, OfflineAudioContext, Progress, Style, limiter, liveContext, quantize, staffNoteToPitch, stereoAnalyser, _ref, _ref1, _ref2;\n\n_ref = system.ui, Progress = _ref.Progress, Modal = _ref.Modal, Style = _ref.Style;\n\nOfflineAudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext;\n\nliveContext = require(\"./lib/audio-context\");\n\nEncoder = require(\"./lib/encoder/index\");\n\n_ref1 = require(\"./lib/util\"), quantize = _ref1.quantize, staffNoteToPitch = _ref1.staffNoteToPitch;\n\n_ref2 = FX = require(\"./lib/fx\"), limiter = _ref2.limiter, stereoAnalyser = _ref2.stereoAnalyser;\n\nFXNetwork = function(destination) {\n var context, fx, gain;\n context = destination.context;\n gain = context.createGain();\n gain.connect(destination);\n fx = FX.choices.reduce(function(o, name) {\n o[name] = FX[name](gain);\n return o;\n }, {});\n return {\n \"default\": gain,\n fx: fx,\n dispose: function() {\n var currentTime;\n currentTime = context.currentTime;\n gain.gain.exponentialRampToValueAtTime(0.001, 0.1 + currentTime);\n gain.gain.setValueAtTime(0, 0.1 + currentTime);\n return setTimeout(function() {\n return gain.disconnect();\n }, 0.25);\n }\n };\n};\n\nmodule.exports = function(I, self) {\n var activeDestination, analysedDestination, animBuffer, bps, bufferAudio, computeElapsedBeats, initPlay, lastContextTime, lastQueuedBeat, liveDestination, lookahead, playTime, playUpcomingSounds, secondsPerMinute, toBeat, wrapping;\n playTime = 0;\n secondsPerMinute = 60;\n lastContextTime = 0;\n lastQueuedBeat = 0;\n lookahead = 0.1;\n toBeat = null;\n wrapping = false;\n self.attrObservable(\"playing\");\n self.analysedDestination = analysedDestination = stereoAnalyser(liveContext.destination);\n liveDestination = limiter(analysedDestination);\n activeDestination = FXNetwork(liveDestination);\n document.addEventListener(\"visibilitychange\", function(e) {\n if (document.hidden) {\n lookahead = 1.25;\n } else {\n lookahead = 0.1;\n }\n return bufferAudio();\n });\n initPlay = function() {\n var context;\n context = liveDestination.context;\n context.resume();\n lastContextTime = context.currentTime;\n lastQueuedBeat = playTime;\n return wrapping = false;\n };\n computeElapsedBeats = function(playTime, elapsedSeconds) {\n var beats, beatsLeft, section, _bps, _ref3;\n if (playTime >= self.length()) {\n playTime -= self.length();\n }\n _ref3 = self.sectionAt(playTime), section = _ref3[0], beatsLeft = _ref3[1];\n _bps = section.tempo() / 60;\n beats = elapsedSeconds * _bps;\n if (beats > beatsLeft) {\n return beatsLeft + computeElapsedBeats(playTime + beatsLeft, elapsedSeconds - beatsLeft / _bps);\n } else {\n return beats;\n }\n };\n playUpcomingSounds = function(currentBeat, fromBeat, toBeat) {\n var dt;\n dt = toBeat - fromBeat;\n if (dt <= 0) {\n return;\n }\n return self.upcomingNotes(fromBeat, dt, function(_arg, section, s) {\n var accidental, beat, instrument, pitch, staffNote;\n beat = _arg[0], staffNote = _arg[1], accidental = _arg[2], instrument = _arg[3];\n pitch = staffNoteToPitch(staffNote, accidental, section.keySignature());\n return self.playNote(instrument, pitch, s, activeDestination.fx[section.presetName()]);\n }, currentBeat);\n };\n bps = function() {\n return self.tempo() / secondsPerMinute;\n };\n bufferAudio = function() {\n var context, currentTime, dt, elapsedBeats, length;\n if (self.playing()) {\n length = self.length();\n if (length < 1 || isNaN(length)) {\n return;\n }\n context = liveDestination.context;\n currentTime = context.currentTime;\n dt = currentTime - lastContextTime;\n if (dt === 0) {\n return;\n }\n lastContextTime = currentTime;\n elapsedBeats = computeElapsedBeats(playTime, dt);\n self.animateNoteElements(playTime, elapsedBeats);\n playTime += elapsedBeats;\n toBeat = playTime + computeElapsedBeats(playTime, lookahead);\n if (wrapping) {\n toBeat -= length;\n playUpcomingSounds(playTime - length, lastQueuedBeat, toBeat);\n } else {\n playUpcomingSounds(playTime, lastQueuedBeat, toBeat);\n if (toBeat >= length && self.loop()) {\n wrapping = true;\n toBeat -= length;\n lastQueuedBeat = 0;\n playUpcomingSounds(playTime - length, lastQueuedBeat, toBeat);\n }\n }\n lastQueuedBeat = Math.max(lastQueuedBeat, toBeat);\n if (playTime >= length) {\n wrapping = false;\n if (self.loop()) {\n playTime -= length;\n self.animateNoteElements(0, playTime);\n } else {\n playTime = 0;\n self.playing(false);\n }\n }\n self.activeSection(self.sectionAt(playTime)[0]);\n }\n };\n setInterval(function() {\n return bufferAudio();\n }, 1000 / 120);\n animBuffer = function() {\n requestAnimationFrame(animBuffer);\n return bufferAudio();\n };\n animBuffer();\n return self.extend({\n exportSong: function(song, opts) {\n var audioChannels, cleanup, err, extension, lengthInSeconds, name, offlineContext, offlineFxNetwork, progressView, samplesPerSecond, songLength, type;\n if (opts == null) {\n opts = {};\n }\n name = opts.name, type = opts.type;\n if (name == null) {\n name = \"song\";\n }\n if (type == null) {\n type = \"mp3\";\n }\n extension = \".\" + type;\n progressView = Progress({\n message: \"Rendering Audio...\"\n });\n Modal.show(progressView.element, {\n cancellable: false\n });\n cleanup = function() {\n return Modal.hide();\n };\n err = function(fn) {\n return function(e) {\n fn();\n throw e;\n };\n };\n songLength = song.length();\n audioChannels = 2;\n samplesPerSecond = 44100;\n lengthInSeconds = song.exportDuration();\n offlineContext = new OfflineAudioContext(audioChannels, samplesPerSecond * lengthInSeconds, samplesPerSecond);\n offlineFxNetwork = FXNetwork(limiter(offlineContext.destination));\n return new Promise(function(resolve, reject) {\n var dt, t, work;\n t = 0;\n dt = 1;\n work = function() {\n var p;\n song.upcomingNotes(t, dt, function(_arg, section, s) {\n var accidental, beat, instrument, keySig, note, presetName, staffNote;\n beat = _arg[0], staffNote = _arg[1], accidental = _arg[2], instrument = _arg[3];\n presetName = section.presetName();\n keySig = section.keySignature();\n note = staffNoteToPitch(staffNote, accidental, keySig);\n return self.playNote(instrument, note, s, offlineFxNetwork.fx[presetName]);\n }, 0);\n t += dt;\n if (t <= songLength) {\n setTimeout(work, 0);\n } else {\n p = offlineContext.startRendering();\n if (p) {\n p.then(resolve, reject);\n } else {\n offlineContext.oncomplete = function(_arg) {\n var renderedBuffer;\n renderedBuffer = _arg.renderedBuffer;\n return resolve(renderedBuffer);\n };\n }\n }\n };\n work();\n }).then(function(audioBuffer) {\n if (type === \"mp3\") {\n progressView.message(\"Encoding mp3...\");\n return Encoder.audioBufferToMP3(audioBuffer);\n } else {\n progressView.message(\"Encoding wav...\");\n return Encoder.audioBufferToWav(audioBuffer);\n }\n }).then(function(blob) {\n return blob.download(name + extension);\n }).then(cleanup, err(cleanup));\n },\n playNote: function(instrumentId, note, time, dest) {\n var buffer, context, gain, pan, panner, pitchShift, rate, sample, source, volume;\n if (note == null) {\n note = 0;\n }\n if (time == null) {\n time = 0;\n }\n if (dest == null) {\n dest = activeDestination.fx[self.presetName()] || liveDestination;\n }\n context = dest.context;\n sample = self.samples()[instrumentId].I;\n if (sample) {\n buffer = sample.buffer, pitchShift = sample.pitchShift, pan = sample.pan, volume = sample.volume;\n note += pitchShift;\n panner = context.createPanner();\n panner.panningModel = 'equalpower';\n panner.setPosition(pan, 0, 1 - Math.abs(pan));\n gain = context.createGain();\n gain.gain.value = volume;\n rate = Math.pow(2, note / 12);\n source = self.playBuffer(buffer, rate, time, panner);\n panner.connect(gain);\n gain.connect(dest);\n return source.onended = function() {\n return gain.disconnect();\n };\n }\n },\n playBuffer: function(buffer, rate, time, dest) {\n var context, source;\n if (rate == null) {\n rate = 1;\n }\n if (time == null) {\n time = 0;\n }\n if (dest == null) {\n dest = liveDestination;\n }\n context = dest.context;\n if (!(time >= 0)) {\n time = 0;\n }\n source = context.createBufferSource();\n source.buffer = buffer;\n source.connect(dest || context.destination);\n source.start(time + context.currentTime);\n source.playbackRate.value = rate;\n return source;\n },\n adjustPlayhead: function(dt, q) {\n if (q == null) {\n q = 0;\n }\n self.setPlayHead(quantize(playTime + dt, q));\n return self.recenterPlayhead();\n },\n setPlayHead: function(t) {\n if (self.playing()) {\n return;\n }\n playTime = Math.min(self.length() - 0.00001, Math.max(t, 0));\n self.activeSection(self.sectionAt(playTime)[0]);\n return playTime;\n },\n bufferTo: function() {\n return toBeat;\n },\n lastQueuedBeat: function() {\n return lastQueuedBeat;\n },\n playTime: function() {\n return playTime;\n },\n playFromStart: function() {\n if (self.playing()) {\n self.stop();\n } else {\n playTime = 0;\n self.playing(true);\n }\n return initPlay();\n },\n pause: function() {\n if (self.playing()) {\n return self.stop();\n } else {\n self.playing(true);\n return initPlay();\n }\n },\n play: function() {\n return self.pause();\n },\n stop: function() {\n self.playing(false);\n activeDestination.dispose();\n return activeDestination = FXNetwork(liveDestination);\n },\n rewind: function() {\n self.stop();\n playTime = 0;\n return self.scrollTo(0);\n },\n reset: function() {\n self.stop();\n playTime = 0;\n return self.activeSection(self.sections()[0]);\n }\n });\n};\n"
},
"player": {
"content": "var ExportTemplate, Modal, OptionTemplate, Pattern, Progress, Sample, Song, StaffView, Undo, animate, generateExportTitle, purchase, rand, _ref;\n\nrequire(\"./lib/extensions\");\n\n_ref = system.ui, Progress = _ref.Progress, Modal = _ref.Modal;\n\nExportTemplate = require(\"./templates/export\");\n\nOptionTemplate = require(\"./templates/option\");\n\nSample = require(\"./sample\");\n\nSong = require(\"./song-v2\");\n\nStaffView = require(\"./staff-view\");\n\nPattern = require(\"./lib/pattern\");\n\nUndo = require(\"./lib/undo\");\n\npurchase = require(\"./lib/stripe-checkout\").purchase;\n\nmodule.exports = function(I, self) {\n var FXPicker, MeterPicker, Template, aboutTemplate, buyNowTemplate, helpTemplate, persistenceElement, song;\n if (I == null) {\n I = {};\n }\n if (self == null) {\n self = Model(I);\n }\n defaults(I, {\n patterns: []\n });\n self.include(require(\"./lib/hotkeys\"));\n self.include(require(\"./lib/midi-input\"));\n self.attrObservable(\"activeSection\", \"purchased\");\n self.attrModels(\"patterns\", Pattern);\n self.attrModel(\"song\", Song);\n song = self.song();\n Sample.loadPack().then(song.loadSettings).then(function() {\n return self.applySampleCSS(Sample.image);\n });\n self.extend({\n notePosition: Observable(\"\"),\n samples: song.settings,\n addNote: function(note) {\n self.unsaved(true);\n self.song().addNote(note);\n return self.pushState(self.getState());\n },\n removeNote: function(note, nearby) {\n var removed;\n self.unsaved(true);\n removed = self.song().removeNote(note, nearby);\n self.pushState(self.getState());\n return removed;\n },\n addPattern: function(beatNote, pattern) {\n self.unsaved(true);\n self.song().addPattern(beatNote, pattern);\n self.pushState(self.getState());\n return self.triggerRerender();\n },\n deleteRange: function(range) {\n self.unsaved(true);\n self.song().deleteRange(range);\n self.pushState(self.getState());\n return self.triggerRerender();\n },\n copyRange: function(range) {\n var pattern;\n pattern = self.song().copyRange(range);\n if (pattern.notes().length) {\n self.activeToolIndex(3);\n return self.activePatternIndex(self.patterns.push(pattern) - 1);\n }\n },\n moveNotes: function(notes, beatDelta, staffDelta) {\n self.unsaved(true);\n notes.forEach(function(note) {\n note[0] += beatDelta;\n return note[1] += staffDelta;\n });\n if (beatDelta !== 0) {\n self.song().resection(notes);\n }\n self.pushState(self.getState());\n return self.triggerRerender(true);\n },\n exportSprites: function() {\n return Sample.exportPNG(self.samples());\n },\n applySampleCSS: function(image) {\n var n, style, _i, _results;\n style = document.querySelector(\"style.samples\");\n if (style == null) {\n style = document.createElement(\"style\");\n }\n style.classList.add(\"samples\");\n n = image.width / 48;\n style.innerHTML = (function() {\n _results = [];\n for (var _i = 0; 0 <= n ? _i < n : _i > n; 0 <= n ? _i++ : _i--){ _results.push(_i); }\n return _results;\n }).apply(this).map(function(i) {\n var x;\n x = -i * 48;\n return \"note.i\" + i + \":after {background: url(\" + image.src + \") \" + x + \"px 0;}\\nnote.i\" + i + \".active:after {background: url(\" + image.src + \") \" + x + \"px 48px;}\\ntools > .i\" + i + \" {background: url(\" + image.src + \") \" + x + \"px 0px;}\\ntools > .i\" + i + \".active {background: #FFC107 url(\" + image.src + \") \" + x + \"px 48px;}\";\n }).join(\"\\n\");\n return document.head.appendChild(style);\n },\n loop: song.loop,\n toggleLoop: function() {\n return self.loop.toggle();\n },\n loopButtonClass: function() {\n if (self.loop()) {\n return \"active\";\n }\n },\n playButtonClass: function() {\n if (self.playing()) {\n return \"active\";\n }\n },\n length: song.length,\n sections: song.sections,\n sectionAt: song.sectionAt,\n tempo: function() {\n return self.activeSection().tempo.apply(null, arguments);\n },\n presetName: function() {\n return self.activeSection().presetName.apply(null, arguments);\n },\n keySignature: function() {\n return self.activeSection().keySignature.apply(null, arguments);\n },\n timeSignature: function() {\n return self.activeSection().timeSignature.apply(null, arguments);\n },\n clearDisabled: function() {\n return !self.activeSection().notes().length;\n },\n clear: function() {\n return Modal.confirm(\"Clear entire song?\").then(function(confirmed) {\n if (confirmed) {\n song.clear();\n self.unsaved(true);\n self.pushState(self.getState());\n return self.triggerRerender();\n }\n });\n },\n about: function() {\n return Modal.show(aboutTemplate, {\n cancellable: true\n });\n },\n showHelp: function() {\n return Modal.show(helpTemplate, {\n cancellable: true\n });\n },\n showPersistenceModal: function() {\n return Modal.show(persistenceElement);\n },\n hiddenIfPurchased: function() {\n if (self.purchased()) {\n return \"hidden\";\n }\n },\n hiddenUnlessPurchased: function() {\n if (!self.purchased()) {\n return \"hidden\";\n }\n },\n purchase: function() {\n return Modal.show(buyNowTemplate, {\n cancellable: true\n });\n },\n oldVersion: function() {\n return window.location = \"https://danielx.net/composer/0.2.0-pre.0/\";\n },\n exportAudio: function() {\n var formatTypes, name, selectedType;\n name = Observable(\"song\");\n selectedType = Observable(\"mp3\");\n formatTypes = [\"mp3\", \"wav\"];\n return Modal.show(ExportTemplate({\n name: name,\n title: generateExportTitle(),\n selectedType: selectedType,\n formatOptionElements: function() {\n return formatTypes.map(function(type) {\n return OptionTemplate({\n text: type,\n value: type\n });\n });\n },\n cancel: function(e) {\n e.preventDefault();\n return Modal.hide();\n },\n submit: function(e) {\n e.preventDefault();\n return self.exportSong(self.song(), {\n name: name(),\n type: selectedType()\n });\n }\n }));\n },\n upcomingNotes: song.upcomingNotes,\n fullscreen: Observable(document.fullscreenElement),\n loadFromURL: function(url) {\n var progressView;\n progressView = Progress({\n value: 0,\n message: \"Loading...\"\n });\n Modal.show(progressView.element, {\n cancellable: false\n });\n return ajax.ajax({\n url: url,\n responseType: \"json\"\n }).progress(function(_arg) {\n var lengthComputable, loaded, total;\n lengthComputable = _arg.lengthComputable, loaded = _arg.loaded, total = _arg.total;\n if (lengthComputable) {\n return progressView.value(loaded / total);\n }\n }).then(self.fromJSON).then(function() {\n return Modal.hide();\n })[\"catch\"](function(e) {\n if (e.statusText) {\n return Modal.alert(\"An error has occurred: \" + e.status + \" - \" + e.statusText);\n } else {\n return Modal.alert(\"An error has occurred: \" + e.message);\n }\n });\n },\n hotkeysTableElement: function() {\n var div;\n div = document.createElement('div');\n div.innerHTML = \"<table>\\n<thead>\\n<tr>\\n<th>Action</th>\\n<th>Key</th>\\n</tr>\\n</thead>\\n<tbody><tr>\\n<td>Select Instrument</td>\\n<td><code>0-9</code></td>\\n</tr>\\n<tr>\\n<td>Select Instrument</td>\\n<td><code>&lt;backtick&gt; 1-7</code></td>\\n</tr>\\n<tr>\\n<td>Select Pattern</td>\\n<td><code>Shift+0-9</code></td>\\n</tr>\\n<tr>\\n<td>Eraser Tool</td>\\n<td><code>e</code></td>\\n</tr>\\n<tr>\\n<td>Selection Tool</td>\\n<td><code>s</code></td>\\n</tr>\\n<tr>\\n<td>Undo</td>\\n<td><code>Ctrl+z</code>, <code>⌘+z</code></td>\\n</tr>\\n<tr>\\n<td>Redo</td>\\n<td><code>Ctrl+y</code>, <code>⌘+y</code></td>\\n</tr>\\n<tr>\\n<td>Play/Pause</td>\\n<td><code>Space</code></td>\\n</tr>\\n<tr>\\n<td>Play from Beginning</td>\\n<td><code>Enter</code></td>\\n</tr>\\n<tr>\\n<td>&nbsp;</td>\\n<td></td>\\n</tr>\\n<tr>\\n<td><strong>Selection</strong></td>\\n<td></td>\\n</tr>\\n<tr>\\n<td>Copy</td>\\n<td><code>c</code></td>\\n</tr>\\n<tr>\\n<td>Cut</td>\\n<td><code>x</code></td>\\n</tr>\\n<tr>\\n<td>Delete</td>\\n<td><code>Delete</code></td>\\n</tr>\\n<tr>\\n<td>Reset</td>\\n<td><code>Esc</code></td>\\n</tr>\\n<tr>\\n<td>&nbsp;</td>\\n<td></td>\\n</tr>\\n<tr>\\n<td><strong>Data</strong></td>\\n<td></td>\\n</tr>\\n<tr>\\n<td>Save</td>\\n<td><code>Ctrl+s</code>, <code>⌘+s</code></td>\\n</tr>\\n<tr>\\n<td>Open</td>\\n<td><code>Ctrl+o</code>, <code>⌘+o</code></td>\\n</tr>\\n<tr>\\n<td>Export</td>\\n<td><code>Ctrl+r</code>, <code>⌘+r</code></td>\\n</tr>\\n<tr>\\n<td>&nbsp;</td>\\n<td></td>\\n</tr>\\n<tr>\\n<td><strong>Meta</strong></td>\\n<td></td>\\n</tr>\\n<tr>\\n<td>About</td>\\n<td><code>F1</code></td>\\n</tr>\\n<tr>\\n<td>Toggle Full screen</td>\\n<td><code>F11</code></td>\\n</tr>\\n<tr>\\n<td>Help</td>\\n<td><code>?</code></td>\\n</tr>\\n</tbody></table>\";\n return div.children[0];\n }\n });\n self.include(require(\"./player-audio\"));\n self.include(require(\"./persistence\"));\n self.include(require(\"./tools\"));\n self.include(StaffView);\n Undo(self);\n self.pushState(self.getState());\n self.activeSection(self.sections()[0]);\n self.noteControlElement = require(\"./views/note-control\")(self).element;\n MeterPicker = require(\"./views/meter-picker\");\n self.meterPickerElement = MeterPicker(self).element;\n FXPicker = require(\"./views/fx-picker\");\n self.fxPickerElement = FXPicker({\n presetName: self.presetName\n }).element;\n Observable(function() {\n var name;\n name = self.presetName();\n return self.fxStyle(FXPicker.styleFor(name));\n });\n require(\"./lib/meter-background\").then(function(timeSignatureMeterBackgroundUrls) {\n var style;\n style = document.createElement(\"style\");\n style.classList.add(\"meter\");\n style.innerHTML = Object.keys(timeSignatureMeterBackgroundUrls).map(function(n) {\n var url;\n url = timeSignatureMeterBackgroundUrls[n];\n return \"song-section.n\" + n + \" {background-image: url(\" + url + \");}\";\n }).join(\"\\n\");\n return document.head.appendChild(style);\n });\n require(\"./views/demo-picker\")(self);\n require(\"./views/settings\")(self);\n require(\"./views/arranger\")(self);\n Template = require(\"./templates/app\");\n self.element = Template({\n actionsElement: require(\"./views/actions\")(self).element,\n toolsElement: require(\"./views/tools\")(self).element,\n patternsElement: require(\"./views/patterns\")(self).element,\n staffElement: self.staffElement,\n notePosition: self.notePosition\n });\n aboutTemplate = require(\"./templates/about\")(self);\n helpTemplate = require(\"./templates/help\")(self);\n buyNowTemplate = require(\"./templates/buy-now\")({\n submit: function(e) {\n e.preventDefault();\n return purchase();\n }\n });\n persistenceElement = require(\"./templates/persistence\")(self);\n animate(function() {\n return self.performDraw();\n });\n Observable(function() {\n song.length();\n return self.rerenderNotes();\n });\n return self;\n};\n\nrand = function(a) {\n return a[Math.floor(Math.random() * a.length)];\n};\n\ngenerateExportTitle = function() {\n var adjective, noun;\n adjective = rand([\"cool\", \"rad\", \"kickin'\", \"bumpin'\", \"sweet\", \"tasty\"]);\n noun = rand([\"banger\", \"track\", \"song\", \"tune\", \"jam\"]);\n return \"Export your \" + adjective + \" \" + noun;\n};\n\nanimate = function(fn) {\n var step;\n step = function() {\n requestAnimationFrame(step);\n return fn();\n };\n return step();\n};\n"
},
"sample": {
"content": "var BASE_PATH, Sample, blobLoader, bufferLoader, context, defaultPack, imgLoader, samples, urlFor;\n\ncontext = require(\"./lib/audio-context\");\n\nbufferLoader = function(url) {\n return new Promise(function(resolve, reject) {\n return fetch(url).then(function(response) {\n if (!response.ok) {\n throw response;\n }\n return response.arrayBuffer();\n }).then(function(buffer) {\n return context.decodeAudioData(buffer, resolve, reject);\n });\n });\n};\n\nblobLoader = function(url) {\n return fetch(url).then(function(response) {\n if (!response.ok) {\n throw response;\n }\n return response.blob();\n });\n};\n\nBASE_PATH = window.location;\n\nurlFor = function(path) {\n return \"\" + BASE_PATH + \"assets/\" + path;\n};\n\nimgLoader = function(url) {\n return new Promise(function(resolve, reject) {\n var image;\n image = new Image;\n image.crossOrigin = true;\n image.src = url;\n image.onload = function() {\n return resolve(image);\n };\n return image.onerror = reject;\n });\n};\n\nmodule.exports = Sample = {\n load: function(data) {\n var sample;\n sample = data.sample;\n return bufferLoader(urlFor(sample)).then(function(buffer) {\n return Object.assign(data, {\n buffer: buffer\n });\n });\n },\n loadPack: function() {\n return imgLoader(urlFor(\"images/default.png\")).then(function(img) {\n Sample.image = img;\n return Promise.all(defaultPack.map(function(sample, i) {\n var canvas, ctx;\n canvas = document.createElement('canvas');\n canvas.width = 48;\n canvas.height = 48;\n ctx = canvas.getContext('2d');\n ctx.drawImage(img, i * 48, 0, 48, 48, 0, 0, 48, 48);\n return new Promise(function(resolve, reject) {\n return canvas.toBlob(function(blob) {\n var url;\n url = URL.createObjectURL(blob);\n sample.cursor = \"url(\" + url + \") \" + 24 + \" \" + 24 + \", default\";\n sample.url = url;\n return resolve();\n });\n });\n }));\n }).then(function() {\n return Promise.all(defaultPack.map(Sample.load));\n });\n },\n exportPNG: function(samples) {\n var canvas, ctx;\n canvas = document.createElement('canvas');\n canvas.width = 48 * samples.length;\n canvas.height = 96;\n ctx = canvas.getContext('2d');\n samples.forEach(function(_arg, i) {\n var activeImage, image, x;\n image = _arg.image, activeImage = _arg.activeImage;\n x = 48 * i;\n ctx.drawImage(image, x, 0);\n return ctx.drawImage(activeImage, x, 48);\n });\n return canvas.toBlob(function(blob) {\n var url;\n url = URL.createObjectURL(blob);\n return window.open(url, \"_blank\");\n });\n }\n};\n\nbufferLoader(\"\" + BASE_PATH + \"assets/erase2.wav\").then(function(buffer) {\n return Sample.fx = {\n eraser: buffer\n };\n});\n\nsamples = {\n synth: {\n sample: \"synth.wav\"\n },\n piano: {\n sample: \"piano.wav\",\n pan: -0.5\n },\n guitar: {\n sample: \"guitar.wav\",\n pan: 0.25\n },\n bass: {\n sample: \"16.wav\"\n },\n horn: {\n sample: \"horn.wav\",\n pan: -0.333\n },\n orch_hit: {\n sample: \"orch_hit.wav\",\n pan: 0.25\n },\n chime: {\n sample: \"5.wav\",\n pan: 0.5\n },\n organ: {\n sample: \"organ.wav\",\n pan: -0.25\n },\n drum: {\n sample: \"drum.wav\"\n },\n snare: {\n sample: \"snare.wav\"\n },\n woodblock: {\n sample: \"14.wav\",\n pan: -0.333\n },\n clap: {\n sample: \"clap.wav\"\n },\n hat: {\n sample: \"hat.wav\",\n pan: 0.333\n },\n baby: {\n sample: \"baby.wav\",\n pan: -0.5\n },\n yoshi: {\n sample: \"yoshi.wav\",\n pan: 0.5\n },\n pig: {\n sample: \"oink.wav\"\n },\n cat: {\n sample: \"cat.wav\",\n pan: 0.25\n },\n dog: {\n sample: \"dog.wav\",\n pan: -0.25\n },\n french: {\n sample: \"french-horn.wav\",\n pan: 0.333,\n pitchShift: 0\n },\n nylon: {\n sample: \"nylon-guitar2.wav\",\n pan: -0.25,\n pitchShift: -12\n },\n snare2: {\n sample: \"snare2.wav\",\n pitchShift: 0\n }\n};\n\ndefaultPack = Object.keys(samples).map(function(name, i) {\n var sample;\n sample = samples[name];\n sample.name = name;\n sample.index = i;\n if (sample.pitchShift == null) {\n sample.pitchShift = -5;\n }\n if (sample.pan == null) {\n sample.pan = 0;\n }\n if (sample.volume == null) {\n sample.volume = 1;\n }\n return sample;\n});\n"
},
"song-v2": {
"content": "\n/*\nSong V2\n=======\n\nA song has a list of sections.\n\nEach section has:\n\nname: string\nlength: beats\nnotes: array<Note>\nsettings: [{\n pan: float [-1, 1]\n pitchShift: float (semitones)\n volume: float\n}, ...]\n */\nvar OldSong, Pattern, Section, Setting, noteCompare, oldInstrumentMapping, pitchToStaffNote, quantize, staffNoteToPitch, _ref,\n __slice = [].slice;\n\n_ref = require(\"./lib/util\"), pitchToStaffNote = _ref.pitchToStaffNote, staffNoteToPitch = _ref.staffNoteToPitch, noteCompare = _ref.noteCompare, quantize = _ref.quantize;\n\nSetting = function(I, self) {\n if (I == null) {\n I = {};\n }\n if (self == null) {\n self = Model(I);\n }\n self.attrObservable(\"url\", \"name\");\n [\"pan\", \"pitchShift\", \"volume\"].forEach(function(name) {\n self[name] = Observable(I[name]);\n return self[name].observe(function(newValue) {\n return I[name] = Number(newValue);\n });\n });\n self.toJSON = function() {\n return {\n pan: I.pan,\n pitchShift: I.pitchShift,\n volume: I.volume\n };\n };\n return self;\n};\n\nPattern = require(\"./lib/pattern\");\n\nSection = require(\"./lib/section\");\n\nOldSong = require(\"/lib/legacy/song\");\n\noldInstrumentMapping = {\n 0: 0,\n 1: 1,\n 2: 2,\n 3: 4,\n 4: 5,\n 5: 8,\n 6: 9,\n 7: 11,\n 8: 16,\n 9: 17\n};\n\nmodule.exports = function(I, self) {\n var sectionFor;\n if (I == null) {\n I = {};\n }\n if (self == null) {\n self = Model(I);\n }\n defaults(I, {\n title: \"Untitled\",\n description: \"\",\n sections: [\n {\n name: \"Section 1\",\n length: 64,\n tempo: 120,\n notes: []\n }\n ],\n settings: [],\n loop: true,\n version: 2\n });\n self.attrObservable(\"loop\");\n self.attrModels(\"sections\", Section);\n self.attrModels(\"settings\", Setting);\n sectionFor = null;\n self.extend({\n clear: function() {\n var section;\n section = self.sections()[0];\n section.notes([]);\n return self.sections([section]);\n },\n loadSettings: function(samples) {\n return self.settings(samples.map(function(sample) {\n return Setting(sample);\n }));\n },\n addPattern: function(_arg, pattern) {\n var note, time;\n time = _arg[0], note = _arg[1];\n return pattern.notes().forEach(function(_arg1) {\n var n, rest, t;\n t = _arg1[0], n = _arg1[1], rest = 3 <= _arg1.length ? __slice.call(_arg1, 2) : [];\n return self.addNote([time + t, note + n].concat(__slice.call(rest)));\n });\n },\n copyRange: function(range) {\n var dt, first, n0, notes, t, t0;\n t = range[0][0];\n dt = range[0][1] - t;\n notes = self.notesWithinRange(range);\n first = notes[0];\n if (first) {\n t0 = first[0], n0 = first[1];\n notes = notes.map(function(_arg) {\n var n, rest, t;\n t = _arg[0], n = _arg[1], rest = 3 <= _arg.length ? __slice.call(_arg, 2) : [];\n return [t - t0, n - n0].concat(__slice.call(rest));\n });\n }\n return Pattern({\n beats: dt,\n notes: notes\n });\n },\n deleteRange: function(range) {\n var time;\n time = 0;\n return self.sections.forEach(function(section) {\n var l;\n l = section.length();\n section.deleteRange([[range[0][0] - time, range[0][1] - time], range[1]]);\n return time += l;\n });\n },\n\n /*\n Pass all the notes that are upcoming to a handler fn\n The handler fn receives:\n \n note - a stable reference to a note that can be used as a key to an element\n section - a reference to the section object the note appears in\n s - seconds after beat t, accounts for different section bpm\n \n i.e. notes that occur within the interval [t, t+dt)\n \n t and dt are in beats\n \n doesn't return notes outside of [0, length)\n \n current is expected to be <= t\n \n if current is < 0 then it is considered to wrap from the end\n \n current is an optional reference time to receive the accumulated seconds\n relative to. This is used in the audio playback.\n */\n upcomingNotes: function(t, dt, fn, current) {\n var accumulatedBeats, accumulatedTime, songDuration, songLength;\n if (current == null) {\n current = t;\n }\n accumulatedBeats = 0;\n accumulatedTime = 0;\n songLength = self.length();\n songDuration = self.duration();\n if (songLength <= 0 || isNaN(songLength)) {\n return;\n }\n if (t >= songLength) {\n return;\n }\n while (current < 0) {\n accumulatedTime += songDuration;\n current += songLength;\n }\n if (current > 0) {\n self.sections().forEach(function(section) {\n var sectionDuration, sectionLength;\n sectionLength = section.length();\n sectionDuration = section.duration();\n if (sectionLength < current) {\n current -= sectionLength;\n return accumulatedTime -= sectionDuration;\n } else if (current > 0) {\n accumulatedTime -= (current / sectionLength) * sectionDuration;\n return current = 0;\n }\n });\n }\n return self.sections().forEach(function(section) {\n var sectionDuration, sectionLength;\n sectionLength = section.length();\n sectionDuration = section.duration();\n section.upcomingNotes(t - accumulatedBeats, dt, function(note) {\n var beat, s;\n beat = note[0];\n s = (beat / sectionLength) * sectionDuration + accumulatedTime;\n return fn(note, section, s);\n });\n accumulatedBeats += sectionLength;\n return accumulatedTime += sectionDuration;\n });\n },\n notesWithin: function(t, dt) {\n var notes;\n notes = [];\n self.upcomingNotes(t, dt, function(note) {\n return notes.push(note);\n });\n return notes;\n },\n notesWithinRange: function(range) {\n var dt, n0, n1, notes, t, _ref1;\n t = range[0][0];\n dt = range[0][1] - t;\n _ref1 = range[1], n0 = _ref1[0], n1 = _ref1[1];\n return notes = self.notesWithin(t, dt + 0.0001).filter(function(_arg) {\n var n, _;\n _ = _arg[0], n = _arg[1];\n return (n0 <= n && n <= n1);\n }).sort(noteCompare);\n },\n sectionAt: function(t) {\n var i, section, sections, time;\n time = 0;\n i = 0;\n sections = self.sections();\n while (section = sections[i]) {\n time += section.length();\n if (t < time) {\n return [section, time - t];\n }\n i++;\n }\n return [];\n },\n addNote: function(note) {\n var t, time;\n time = 0;\n t = note[0];\n return self.sections.forEach(function(section) {\n var l, _ref1;\n l = section.length();\n if ((0 <= (_ref1 = t - time) && _ref1 < l)) {\n note[0] -= time;\n section.addNote(note);\n sectionFor.set(note, section);\n }\n return time += l;\n });\n },\n removeNote: function(note, nearby) {\n var removed, t, time;\n time = 0;\n t = note[0];\n removed = false;\n self.sections.forEach(function(section) {\n var l, _ref1;\n l = section.length();\n if ((0 <= (_ref1 = t - time) && _ref1 < l)) {\n note[0] -= time;\n removed = section.removeNote(note, nearby);\n }\n return time += l;\n });\n return removed;\n },\n sectionBeginsAt: function(s) {\n var ret, t;\n t = 0;\n ret = void 0;\n self.sections.forEach(function(section) {\n if (section === s) {\n ret = t;\n }\n return t += s.length();\n });\n return ret;\n },\n duration: function() {\n return self.sections.reduce(function(total, section) {\n return total + section.duration();\n }, 0);\n },\n exportDuration: function() {\n var last;\n last = self.sections().length - 1;\n return self.sections.reduce(function(total, section, i) {\n if (i === last) {\n return total + section.nonEmptyDuration();\n } else {\n return total + section.duration();\n }\n }, 0);\n },\n length: function() {\n return self.sections().reduce(function(count, section) {\n return count + section.length();\n }, 0);\n },\n reset: function() {\n sectionFor = new Map;\n return self.upcomingNotes(0, self.length(), function(note, section) {\n return sectionFor.set(note, section);\n });\n },\n resection: function(notes) {\n return notes.forEach(function(note) {\n var section, sectionStart, t;\n section = sectionFor.get(note);\n t = note[0];\n if ((t < 0) || (t >= section.length())) {\n sectionStart = self.sectionBeginsAt(section);\n if (sectionStart === 0 && t < 0) {\n return;\n }\n if (section.removeNoteByReference(note)) {\n note[0] += sectionStart;\n return self.addNote(note);\n }\n }\n });\n },\n toJSON: function() {\n return Object.assign({}, I, {\n settings: self.settings.map(function(s) {\n return s.toJSON();\n })\n });\n },\n fromJSON: function(data) {\n var lastTime, newNotes, notes, section, song;\n if (data.patterns || data.notes) {\n song = OldSong().fromJSON(data);\n notes = song.upcomingNotes(0, song.size());\n newNotes = notes.map(function(_arg) {\n var beat, instrument, note;\n beat = _arg[0], note = _arg[1], instrument = _arg[2];\n return [beat].concat(__slice.call(pitchToStaffNote(note)), [oldInstrumentMapping[instrument]]);\n });\n section = self.sections()[0];\n section.notes(newNotes);\n lastTime = newNotes.reduce(function(max, _arg) {\n var t;\n t = _arg[0];\n return Math.max(t, max);\n }, 0);\n section.length(quantize(lastTime + 1.99999, 4));\n section.tempo(song.tempo());\n } else {\n self.sections(data.sections.map(function(s) {\n return Section(s);\n }));\n if (data.settings) {\n debugger;\n data.settings.forEach(function(s, i) {\n var setting;\n setting = self.settings()[i];\n return Object.keys(s).forEach(function(key) {\n return setting[key](s[key]);\n });\n });\n }\n if (data.version === void 0) {\n self.sections.forEach(function(section) {\n notes = section.I.notes;\n return notes.forEach(function(n, i) {\n var accidental, beat, instrument, pitch, staffNote, _ref1, _ref2;\n _ref1 = notes[i], beat = _ref1[0], staffNote = _ref1[1], accidental = _ref1[2], instrument = _ref1[3];\n pitch = staffNoteToPitch(staffNote, accidental) + 5;\n _ref2 = pitchToStaffNote(pitch), staffNote = _ref2[0], accidental = _ref2[1];\n notes[i][1] = staffNote;\n return notes[i][2] = accidental;\n });\n });\n }\n }\n self.reset();\n return self;\n }\n });\n self.reset();\n return self;\n};\n"
},
"staff-view": {
"content": "var CLEF_OFFSET, NoteTemplate, PIXELS_PER_BEAT, SectionTemplate, accidentalToSymbol, animateNoteElement, clamp, composedPath, noteAnimationHandler, noteName, quantize, staffNoteToPitch, updateMeasureNumbers, _ref, _ref1;\n\nNoteTemplate = require(\"./templates/note\");\n\nSectionTemplate = require(\"./templates/section\");\n\n_ref = require(\"./lib/util\"), accidentalToSymbol = _ref.accidentalToSymbol, clamp = _ref.clamp, composedPath = _ref.composedPath, noteName = _ref.noteName, quantize = _ref.quantize, staffNoteToPitch = _ref.staffNoteToPitch;\n\n_ref1 = require(\"./const\"), CLEF_OFFSET = _ref1.CLEF_OFFSET, PIXELS_PER_BEAT = _ref1.PIXELS_PER_BEAT;\n\nnoteAnimationHandler = function() {\n this.classList.remove(\"active\");\n return this.removeEventListener(\"animationend\", noteAnimationHandler);\n};\n\nanimateNoteElement = function(element) {\n if (element.classList.contains(\"active\")) {\n return;\n }\n element.classList.add(\"active\");\n return element.addEventListener(\"animationend\", noteAnimationHandler);\n};\n\nupdateMeasureNumbers = function(sectionElement, t, section) {\n var beatGroup, beatsPerMeasure, measuresPerNumber, numberElements, sectionLength, t1, t2;\n sectionLength = section.length();\n numberElements = sectionElement.querySelectorAll('span.measure-number');\n beatsPerMeasure = section.numerator();\n measuresPerNumber = 4;\n beatGroup = measuresPerNumber * beatsPerMeasure;\n t1 = quantize(t, beatGroup);\n if (t1 < t) {\n t1 += beatGroup;\n }\n if (t1 < 0) {\n t1 = 0;\n }\n t2 = t1 + beatGroup;\n if (t1 < sectionLength) {\n numberElements[0].innerText = t1 / beatsPerMeasure + 1;\n numberElements[0].style.left = t1 * PIXELS_PER_BEAT + \"px\";\n }\n if (t2 < sectionLength) {\n numberElements[1].style.display = \"initial\";\n numberElements[1].innerText = t2 / beatsPerMeasure + 1;\n return numberElements[1].style.left = t2 * PIXELS_PER_BEAT + \"px\";\n } else {\n return numberElements[1].style.display = \"none\";\n }\n};\n\nmodule.exports = function(I, self) {\n var adjustScroll, autoscroll, beatNoteInRange, beatToStaffPosition, bufferEndElement, bufferStartElement, clearNoteCache, clientWidth, element, eventToBeatNote, eventToStaffPosition, noteElementCache, notesElement, playheadElement, positionToStaffNote, recenterPlayhead, renderNote, renderSection, rerenderTriggered, scrollLeft, sectionElementCache, staffElement, staffNoteToPosition, staffPositionToBeat, staffPositionToBeatNote;\n if (I == null) {\n I = {};\n }\n if (self == null) {\n self = Model(I);\n }\n defaults(I, {\n gamut: [-14, 14],\n quantize: 1 / 4,\n accidentalModifier: 0\n });\n self.attrObservable(\"gamut\", \"quantize\", \"accidentalModifier\", \"fxStyle\");\n rerenderTriggered = false;\n clearNoteCache = false;\n scrollLeft = null;\n clientWidth = null;\n beatNoteInRange = function(_arg) {\n var beat, noteMax, noteMin, staffNote, _ref2;\n beat = _arg[0], staffNote = _arg[1];\n _ref2 = self.gamut(), noteMin = _ref2[0], noteMax = _ref2[1];\n return staffNote >= noteMin && staffNote <= noteMax && beat >= 0;\n };\n staffPositionToBeatNote = function(_arg) {\n var a, beat, staffNote, t, y;\n t = _arg.t, y = _arg.y, a = _arg.a;\n staffNote = positionToStaffNote(y);\n beat = quantize(t, self.quantize());\n return [beat, staffNote, a];\n };\n element = require(\"./templates/staff\")({\n fxStyle: self.fxStyle,\n contextmenu: function(e) {\n return e.preventDefault();\n },\n dragstart: function(e) {\n return e.preventDefault();\n },\n scroll: function(e) {\n scrollLeft = element.scrollLeft, clientWidth = element.clientWidth;\n return rerenderTriggered = true;\n },\n mousedown: function(e) {\n var beatNote, buttonPressed, sp;\n buttonPressed = composedPath(e.target).some(function(el) {\n return el.tagName === \"BUTTON\";\n });\n if (buttonPressed) {\n return;\n }\n sp = eventToStaffPosition(e);\n beatNote = eventToBeatNote(e);\n self.activeTool().down(self, beatNote, e, sp);\n return rerenderTriggered = true;\n },\n mousemove: function(e) {\n var accidental, beat, beatNote, staffNote;\n beatNote = eventToBeatNote(e);\n if (beatNoteInRange(beatNote)) {\n beat = beatNote[0], staffNote = beatNote[1], accidental = beatNote[2];\n self.notePosition(\"Beat: \" + ((beat + 1).toFixed(2)) + \"\\nNote: \" + (noteName(staffNoteToPitch(staffNote, accidental))));\n } else {\n self.notePosition(\"\");\n }\n return self.activeTool().move(self, beatNote, e);\n },\n mouseup: function(e) {\n var beatNote;\n beatNote = eventToBeatNote(e);\n return self.activeTool().up(self, beatNote, e);\n },\n repeatClass: function() {\n if (!self.loop()) {\n return \"hidden\";\n }\n },\n selectionClass: self.selectionClass,\n selectionCopy: self.selectionCopy,\n selectionCut: self.selectionCut,\n selectionDelete: self.selectionDelete,\n selectionReset: self.selectionReset,\n selectionMoveUp: self.selectionMoveUp,\n selectionMoveDown: self.selectionMoveDown,\n selectionMoveLeft: self.navigateLeft,\n selectionMoveRight: self.navigateRight\n });\n playheadElement = element.querySelector(\"playhead\");\n staffElement = element.querySelector(\"staff\");\n notesElement = staffElement.querySelector(\"notes\");\n self.selectionElement = staffElement.querySelector(\"selection\");\n self.patternPreviewElement = staffElement.querySelector(\"pattern-preview\");\n bufferStartElement = element.querySelector(\"playhead.buffer-start\");\n bufferEndElement = element.querySelector(\"playhead.buffer-end\");\n setTimeout(function() {\n return scrollLeft = element.scrollLeft, clientWidth = element.clientWidth, element;\n });\n window.addEventListener(\"resize\", function(e) {\n scrollLeft = element.scrollLeft, clientWidth = element.clientWidth;\n return rerenderTriggered = true;\n });\n beatToStaffPosition = function(t) {\n return PIXELS_PER_BEAT * t + CLEF_OFFSET;\n };\n staffPositionToBeat = function(x) {\n return (x - CLEF_OFFSET) / PIXELS_PER_BEAT;\n };\n eventToStaffPosition = function(e) {\n var a, ctrlKey, left, metaKey, pageX, pageY, shiftKey, t, top, x, y, _ref2;\n pageX = e.pageX, pageY = e.pageY, shiftKey = e.shiftKey, ctrlKey = e.ctrlKey, metaKey = e.metaKey;\n _ref2 = staffElement.getBoundingClientRect(), top = _ref2.top, left = _ref2.left;\n x = pageX - left;\n y = pageY - top;\n t = staffPositionToBeat(x);\n a = self.accidentalModifier();\n if (a === 0) {\n if (shiftKey) {\n a = 1;\n } else if (ctrlKey || metaKey) {\n a = -1;\n }\n } else if (a === 1 && (ctrlKey || metaKey)) {\n a = 0;\n } else if (a === -1 && shiftKey) {\n a = 0;\n }\n return {\n t: t,\n y: y,\n a: a\n };\n };\n eventToBeatNote = function(e) {\n return staffPositionToBeatNote(eventToStaffPosition(e));\n };\n positionToStaffNote = function(y) {\n return Math.round((337 - y) / 24);\n };\n staffNoteToPosition = function(n) {\n return 337 - 24 * n;\n };\n renderNote = function(datum) {\n var a, accidental, beat, instrument, staffNote;\n beat = datum[0], staffNote = datum[1], accidental = datum[2], instrument = datum[3];\n a = accidentalToSymbol(accidental);\n return NoteTemplate({\n note: noteName(staffNoteToPitch(staffNote)),\n accidental: a,\n instrument: \"i\" + instrument,\n style: {\n top: \"\" + (staffNote * -24 - 24 + 240) + \"px\",\n left: \"\" + (beat * PIXELS_PER_BEAT - 24) + \"px\"\n }\n });\n };\n renderSection = function(section, startBeat) {\n var el, length;\n el = sectionElementCache.get(section);\n if (!el) {\n el = SectionTemplate({\n c1: function() {\n var sig;\n sig = section.keySignature();\n if (sig > 0) {\n return \"s\";\n } else if (sig < 0) {\n return \"f\";\n }\n },\n c2: function() {\n return this.c1() + Math.abs(section.keySignature());\n },\n meterClass: function() {\n var n;\n n = section.numerator();\n return \"n\" + n;\n }\n });\n sectionElementCache.set(section, el);\n }\n length = section.length();\n Object.assign(el.style, {\n left: \"\" + (startBeat * PIXELS_PER_BEAT) + \"px\",\n width: \"\" + (length * PIXELS_PER_BEAT) + \"px\"\n });\n if (!el.parentElement) {\n notesElement.appendChild(el);\n }\n return el;\n };\n autoscroll = 0;\n adjustScroll = 0;\n recenterPlayhead = false;\n self.samples.observe(function() {\n return self.setCursor();\n });\n self.activeInstrument.observe(function(instrument) {\n return self.setCursor();\n });\n self.activeToolIndex.observe(function() {\n return self.setCursor();\n });\n noteElementCache = new Map;\n sectionElementCache = new Map;\n return self.extend({\n autoscrolling: function(v) {\n return autoscroll = v;\n },\n scrollTo: function(x) {\n return element.scrollLeft = x;\n },\n adjustScroll: function(dt) {\n return adjustScroll = dt;\n },\n recenterPlayhead: function() {\n return recenterPlayhead = true;\n },\n beatNoteInRange: beatNoteInRange,\n beatNoteToStaffPosition: function(_arg) {\n var beat, staffNote;\n beat = _arg[0], staffNote = _arg[1];\n return {\n x: beatToStaffPosition(beat),\n y: staffNoteToPosition(staffNote)\n };\n },\n staffElement: element,\n performDraw: function() {\n var p;\n if (autoscroll) {\n element.scrollLeft += autoscroll * PIXELS_PER_BEAT / 60;\n }\n p = Math.floor(beatToStaffPosition(self.playTime()));\n playheadElement.style.left = p + \"px\";\n staffElement.style.width = CLEF_OFFSET + PIXELS_PER_BEAT * self.song().length() + \"px\";\n if (self.playing() || recenterPlayhead) {\n element.scrollLeft = p - clientWidth / 2 + 56;\n } else if (adjustScroll) {\n element.scrollLeft += adjustScroll * PIXELS_PER_BEAT;\n }\n if (rerenderTriggered) {\n rerenderTriggered = false;\n self.rerenderNotes();\n }\n adjustScroll = 0;\n recenterPlayhead = false;\n },\n triggerRerender: function(force) {\n rerenderTriggered = true;\n if (force) {\n return clearNoteCache = true;\n }\n },\n renderNote: renderNote,\n rerenderNotes: function() {\n var notesToRemove, notesToRender, sectionStart, sectionsToRender, t0, t1, x0, x1;\n x0 = scrollLeft - 48 - 48;\n x1 = x0 + clientWidth + 96;\n t0 = staffPositionToBeat(x0);\n t1 = staffPositionToBeat(x1);\n if (clearNoteCache) {\n Array.from(noteElementCache.keys()).filter(function(note) {\n var noteElement;\n noteElement = noteElementCache.get(note);\n noteElement.remove();\n return noteElementCache[\"delete\"](note);\n });\n clearNoteCache = false;\n }\n sectionStart = 0;\n sectionsToRender = new Set;\n self.sections().forEach(function(section) {\n var sectionLength;\n sectionsToRender.add(section);\n renderSection(section, sectionStart);\n sectionLength = section.length();\n updateMeasureNumbers(sectionElementCache.get(section), t0 - sectionStart, section);\n return sectionStart += sectionLength;\n });\n Array.from(sectionElementCache.keys()).forEach(function(section) {\n var el;\n if (!sectionsToRender.has(section)) {\n el = sectionElementCache.get(section);\n el.remove();\n return sectionElementCache[\"delete\"](section);\n }\n });\n notesToRender = new Set;\n self.upcomingNotes(t0, t1 - t0, function(note, section) {\n var noteElement, sectionElement;\n notesToRender.add(note);\n if (!noteElementCache.has(note)) {\n noteElement = renderNote(note);\n sectionElement = sectionElementCache.get(section);\n sectionElement.appendChild(noteElement);\n return noteElementCache.set(note, noteElement);\n }\n });\n return notesToRemove = Array.from(noteElementCache.keys()).filter(function(note) {\n var noteElement;\n if (!notesToRender.has(note)) {\n noteElement = noteElementCache.get(note);\n noteElement.remove();\n return noteElementCache[\"delete\"](note);\n }\n });\n },\n animateNoteElements: function(t, dt) {\n var notes;\n return notes = self.upcomingNotes(t, dt, function(note) {\n var el;\n el = noteElementCache.get(note);\n if (el) {\n return animateNoteElement(el);\n }\n });\n },\n activeSample: function() {\n return self.samples()[self.activeInstrument()];\n },\n setCursor: function() {\n var sample;\n if (self.activeToolIndex() === 0) {\n if (sample = self.activeSample().I) {\n return document.body.style.cursor = sample.cursor;\n }\n } else {\n return document.body.style.cursor = self.activeTool().cursor;\n }\n }\n });\n};\n"
},
"style": {
"content": "module.exports = \"@font-face {\\n font-display: auto;\\n font-family: 'Chicago';\\n src: url(\\\"fonts/chicago.woff2\\\") format('woff2'), url(\\\"fonts/chicago.woff\\\") format('woff');\\n font-weight: normal;\\n font-style: normal;\\n}\\n* {\\n box-sizing: border-box;\\n}\\nimg {\\n max-width: 100%;\\n}\\n.hidden {\\n display: none !important;\\n}\\n#modal > * {\\n border: 1px solid #673ab7;\\n border-radius: 4px;\\n box-shadow: 1px 2px 0px #673ab7;\\n color: inherit;\\n padding: 1rem;\\n}\\n#modal > * > h1,\\n#modal > * > h2 {\\n margin-top: 0;\\n}\\n#modal > section.purchase {\\n background-color: transparent;\\n border: none;\\n border-radius: 0;\\n box-shadow: none;\\n padding: 0;\\n}\\n#modal > .publish > p.status:empty {\\n margin: 0;\\n}\\n#modal > .publish > pre {\\n user-select: all;\\n}\\n#modal > .publish > actions {\\n display: flex;\\n}\\n#modal > .publish > actions > button:last-child {\\n margin-left: auto;\\n}\\n:focus {\\n color: #fff;\\n background-color: #673ab7;\\n outline: none;\\n}\\nhtml,\\nbody {\\n height: 100%;\\n}\\nbody {\\n color: #241440;\\n display: flex;\\n font-family: Chicago, sans-serif;\\n font-size: 16px;\\n line-height: 1rem;\\n margin: 0;\\n overflow: hidden;\\n user-select: none;\\n}\\np {\\n font-family: sans-serif;\\n}\\ninput,\\ntextarea,\\nselect,\\nbutton {\\n font-family: inherit;\\n}\\ninput {\\n background-color: #ede7f6;\\n border: 1px solid #673ab7;\\n border-radius: 4px;\\n box-shadow: 1px 2px 0px #673ab7 inset;\\n color: #673ab7;\\n font-size: inherit;\\n padding: 2px 0.25em;\\n}\\ninput[type=\\\"number\\\"] {\\n -moz-appearance: textfield;\\n}\\ninput:focus {\\n background-color: #ffc107;\\n color: rgba(0,0,0,0.69);\\n}\\nnote {\\n font-size: 48px;\\n height: 48px;\\n -ms-interpolation-mode: nearest-neighbor;\\n image-rendering: crisp-edges;\\n image-rendering: pixelated;\\n position: absolute;\\n width: 48px;\\n}\\nnote.active {\\n animation-name: note-active;\\n animation-duration: 0.25s;\\n}\\nnote::after {\\n align-items: center;\\n background-repeat: no-repeat;\\n background-position: 100% 50%;\\n content: \\\"\\\";\\n display: flex;\\n position: absolute;\\n left: 0;\\n top: 0;\\n width: 100%;\\n height: 100%;\\n text-indent: -12px;\\n}\\nnote.♭::after {\\n content: \\\"♭\\\";\\n}\\nnote.♯::after {\\n content: \\\"♯\\\";\\n}\\nnote.C4::before,\\nnote.A5::before,\\nnote.C6::before,\\nnote.C2::before,\\nnote.E2::before,\\nnote.B5::before,\\nnote.D2::before {\\n content: \\\"\\\";\\n width: 48px;\\n left: 0px;\\n top: 23px;\\n position: absolute;\\n height: 0;\\n border-bottom: 2px solid #000;\\n}\\nnote.B5::before {\\n top: 40px;\\n}\\nnote.D2::before {\\n top: -8px;\\n}\\nsong-section {\\n height: 483px;\\n position: absolute;\\n top: -241px;\\n}\\nsong-section::after {\\n content: \\\"\\\";\\n border-right: 2px solid #000;\\n height: 100%;\\n position: absolute;\\n right: 10px;\\n}\\nsong-section > span.measure-number {\\n background-color: rgba(255,255,255,0.938);\\n border: 1px solid #000;\\n box-shadow: 1px 1px 0 0 rgba(0,0,0,0.5);\\n font-style: italic;\\n left: 0px;\\n padding: 2px 6px 2px 4px;\\n position: absolute;\\n top: -132px;\\n}\\nsong-section > div.key-signature.s > ::after {\\n content: \\\"♯\\\";\\n}\\nsong-section > div.key-signature.f > ::after {\\n content: \\\"♭\\\";\\n}\\nsong-section > div.key-signature > * {\\n display: none;\\n font-size: 96px;\\n height: 48px;\\n position: absolute;\\n width: 48px;\\n}\\nsong-section > div.key-signature > *::after {\\n align-items: center;\\n display: flex;\\n height: 100%;\\n width: 100%;\\n}\\nsong-section > div.key-signature.s1 > :nth-child(1),\\nsong-section > div.key-signature.s2 > :nth-child(1),\\nsong-section > div.key-signature.s3 > :nth-child(1),\\nsong-section > div.key-signature.s4 > :nth-child(1),\\nsong-section > div.key-signature.s5 > :nth-child(1),\\nsong-section > div.key-signature.s6 > :nth-child(1),\\nsong-section > div.key-signature.s7 > :nth-child(1) {\\n display: initial;\\n top: -24px;\\n left: -96px;\\n}\\nsong-section > div.key-signature.s2 > :nth-child(2),\\nsong-section > div.key-signature.s3 > :nth-child(2),\\nsong-section > div.key-signature.s4 > :nth-child(2),\\nsong-section > div.key-signature.s5 > :nth-child(2),\\nsong-section > div.key-signature.s6 > :nth-child(2),\\nsong-section > div.key-signature.s7 > :nth-child(2) {\\n display: initial;\\n top: 48px;\\n left: -72px;\\n}\\nsong-section > div.key-signature.s3 > :nth-child(3),\\nsong-section > div.key-signature.s4 > :nth-child(3),\\nsong-section > div.key-signature.s5 > :nth-child(3),\\nsong-section > div.key-signature.s6 > :nth-child(3),\\nsong-section > div.key-signature.s7 > :nth-child(3) {\\n display: initial;\\n top: -48px;\\n left: -48px;\\n}\\nsong-section > div.key-signature.s4 > :nth-child(4),\\nsong-section > div.key-signature.s5 > :nth-child(4),\\nsong-section > div.key-signature.s6 > :nth-child(4),\\nsong-section > div.key-signature.s7 > :nth-child(4) {\\n display: initial;\\n top: 24px;\\n left: -24px;\\n}\\nsong-section > div.key-signature.s5 > :nth-child(5),\\nsong-section > div.key-signature.s6 > :nth-child(5),\\nsong-section > div.key-signature.s7 > :nth-child(5) {\\n display: initial;\\n top: 96px;\\n left: 0;\\n}\\nsong-section > div.key-signature.s6 > :nth-child(6),\\nsong-section > div.key-signature.s7 > :nth-child(6) {\\n display: initial;\\n top: 0px;\\n left: 24px;\\n}\\nsong-section > div.key-signature.s7 > :nth-child(7) {\\n display: initial;\\n top: 72px;\\n left: 48px;\\n}\\nsong-section > div.key-signature.f1 > :nth-child(1),\\nsong-section > div.key-signature.f2 > :nth-child(1),\\nsong-section > div.key-signature.f3 > :nth-child(1),\\nsong-section > div.key-signature.f4 > :nth-child(1),\\nsong-section > div.key-signature.f5 > :nth-child(1),\\nsong-section > div.key-signature.f6 > :nth-child(1),\\nsong-section > div.key-signature.f7 > :nth-child(1) {\\n display: initial;\\n top: 72px;\\n left: -96px;\\n}\\nsong-section > div.key-signature.f2 > :nth-child(2),\\nsong-section > div.key-signature.f3 > :nth-child(2),\\nsong-section > div.key-signature.f4 > :nth-child(2),\\nsong-section > div.key-signature.f5 > :nth-child(2),\\nsong-section > div.key-signature.f6 > :nth-child(2),\\nsong-section > div.key-signature.f7 > :nth-child(2) {\\n display: initial;\\n top: 0px;\\n left: -72px;\\n}\\nsong-section > div.key-signature.f3 > :nth-child(3),\\nsong-section > div.key-signature.f4 > :nth-child(3),\\nsong-section > div.key-signature.f5 > :nth-child(3),\\nsong-section > div.key-signature.f6 > :nth-child(3),\\nsong-section > div.key-signature.f7 > :nth-child(3) {\\n display: initial;\\n top: 96px;\\n left: -48px;\\n}\\nsong-section > div.key-signature.f4 > :nth-child(4),\\nsong-section > div.key-signature.f5 > :nth-child(4),\\nsong-section > div.key-signature.f6 > :nth-child(4),\\nsong-section > div.key-signature.f7 > :nth-child(4) {\\n display: initial;\\n top: 24px;\\n left: -24px;\\n}\\nsong-section > div.key-signature.f5 > :nth-child(5),\\nsong-section > div.key-signature.f6 > :nth-child(5),\\nsong-section > div.key-signature.f7 > :nth-child(5) {\\n display: initial;\\n top: -48px;\\n left: 0;\\n}\\nsong-section > div.key-signature.f6 > :nth-child(6),\\nsong-section > div.key-signature.f7 > :nth-child(6) {\\n display: initial;\\n top: 48px;\\n left: 24px;\\n}\\nsong-section > div.key-signature.f7 > :nth-child(7) {\\n display: initial;\\n top: -24px;\\n left: 48px;\\n}\\ntd > select {\\n width: 100%;\\n}\\ntd > input {\\n border-radius: 0;\\n box-shadow: none;\\n}\\ntd > aside.fx-picker > label {\\n display: none;\\n}\\ntd.sprite {\\n text-align: center;\\n vertical-align: middle;\\n}\\ntd.sprite > img {\\n margin-right: 1rem;\\n vertical-align: middle;\\n}\\ntd.input > input[type=number] {\\n display: block;\\n margin: auto;\\n width: 60px;\\n}\\nsection.settings {\\n overflow: auto;\\n padding: 1rem;\\n position: relative;\\n}\\nsection.settings > h2 {\\n margin: 0 0 1rem;\\n}\\nsection.settings > button.close {\\n position: absolute;\\n top: 1rem;\\n right: 1rem;\\n}\\nsection.settings > table {\\n margin: 0 -8px;\\n width: calc(100% + 16px);\\n}\\nsection.demo-picker {\\n overflow: auto;\\n padding: 1rem;\\n position: relative;\\n}\\nsection.demo-picker > h2 {\\n margin: 0 0 1rem;\\n}\\nsection.demo-picker > button.close {\\n position: absolute;\\n top: 1rem;\\n right: 1rem;\\n}\\nsection.demo-picker > table {\\n font-size: 18px;\\n}\\nsection.demo-picker > table > tbody > tr {\\n cursor: pointer;\\n line-height: 2rem;\\n}\\nsection.demo-picker > table > tbody > tr:hover {\\n background-color: rgba(103,58,183,0.19);\\n}\\nviewport {\\n background-attachment: local;\\n background-color: #ede7f6;\\n display: flex;\\n height: 100%;\\n align-items: center;\\n overflow-x: scroll;\\n overflow-y: hidden;\\n}\\nviewport > staff {\\n background-color: rgba(255,255,255,0.938);\\n border: 1px solid rgba(0,0,0,0.5);\\n box-sizing: content-box;\\n box-shadow: 1px 1px 0 0 rgba(0,0,0,0.5);\\n display: block;\\n flex: 0 0 auto;\\n padding: 96px 0;\\n position: relative;\\n margin: 0 48px;\\n z-index: 0;\\n}\\nviewport > staff::after {\\n border-left: 8px solid #000;\\n content: \\\"\\\";\\n position: absolute;\\n right: -48px;\\n top: 96px;\\n width: 48px;\\n height: calc(100% - 192px);\\n z-index: -1;\\n}\\nviewport > staff > z-meter {\\n display: block;\\n position: absolute;\\n left: 256px;\\n top: 96px;\\n height: calc(100% - 192px);\\n width: calc(100% - 256px);\\n}\\nviewport > staff > z-meter::before {\\n content: \\\"\\\";\\n border-left: 2px solid #000;\\n position: absolute;\\n height: 100%;\\n left: -257px;\\n}\\nviewport > staff > z-meter > div.repeat {\\n position: absolute;\\n right: 0;\\n height: 100%;\\n}\\nviewport > staff > z-meter > div.repeat::before,\\nviewport > staff > z-meter > div.repeat::after {\\n content: \\\"\\\";\\n display: block;\\n background-color: #000;\\n border-radius: 100%;\\n position: absolute;\\n width: 16px;\\n height: 16px;\\n right: 16px;\\n top: 66px;\\n}\\nviewport > staff > z-meter > div.repeat::after {\\n top: 114px;\\n}\\nviewport > staff > z-meter > div.repeat.bass {\\n position: relative;\\n top: 288px;\\n}\\nviewport > staff > notes {\\n display: block;\\n position: absolute;\\n left: 256px;\\n top: 337px;\\n}\\nviewport > staff > pattern-preview {\\n opacity: 0.5;\\n position: absolute;\\n}\\nviewport > staff > selection {\\n background-color: rgba(103,58,183,0.25);\\n border: 2px dashed #673ab7;\\n position: absolute;\\n left: -150px;\\n z-index: 20;\\n}\\nviewport > staff > selection > button {\\n display: none;\\n position: absolute;\\n top: 0;\\n left: 0;\\n right: 0;\\n bottom: 0;\\n margin: auto;\\n width: 48px;\\n height: 48px;\\n}\\nviewport > staff > selection > button.up {\\n bottom: calc(100% + 4px);\\n top: auto;\\n}\\nviewport > staff > selection > button.down {\\n top: calc(100% + 4px);\\n bottom: auto;\\n}\\nviewport > staff > selection > button.left {\\n right: calc(100% + 4px);\\n left: auto;\\n}\\nviewport > staff > selection > button.right {\\n left: calc(100% + 4px);\\n right: auto;\\n}\\nviewport > staff > selection > actions {\\n display: none;\\n position: absolute;\\n}\\nviewport > staff > selection > actions > button {\\n margin-left: 4px;\\n}\\nviewport > staff > selection.t > actions {\\n top: 0;\\n}\\nviewport > staff > selection.b > actions {\\n bottom: 0;\\n}\\nviewport > staff > selection.l > actions {\\n left: 0;\\n}\\nviewport > staff > selection.r > actions {\\n right: 0;\\n}\\nviewport > staff > selection.set > actions {\\n display: flex;\\n}\\nviewport > staff > selection.set > button {\\n display: block;\\n}\\nviewport > staff > lines {\\n display: block;\\n margin-bottom: 48px;\\n}\\nviewport > staff > lines:nth-child(2) {\\n margin-bottom: 0;\\n}\\nviewport > staff > lines:nth-child(2) > line:last-child {\\n height: 0;\\n}\\nviewport > staff > lines > line {\\n border-top: 3px solid #000;\\n display: block;\\n height: 48px;\\n}\\nviewport > staff > playhead {\\n border-left: 1px solid rgba(103,58,183,0.5);\\n border-right: 1px solid rgba(103,58,183,0.5);\\n position: absolute;\\n top: 0;\\n height: 674px;\\n width: 2px;\\n z-index: 10;\\n}\\nviewport > staff > playhead.buffer-start {\\n display: none;\\n border-color: rgba(255,0,0,0.5);\\n}\\nviewport > staff > playhead.buffer-end {\\n display: none;\\n border-color: rgba(0,0,255,0.5);\\n}\\nviewport > staff > img {\\n position: absolute;\\n height: 600px;\\n top: -94px;\\n left: -120px;\\n}\\nviewport > staff > img:last-child {\\n left: -100px;\\n top: 181px;\\n}\\nform > label {\\n display: block;\\n}\\nform > label > h3 {\\n font-size: 1rem;\\n}\\nform > label > h3.inline {\\n display: inline-block;\\n margin-right: 0.5em;\\n}\\nform > label > input {\\n padding-left: 2px;\\n width: 100%;\\n}\\nform > actions {\\n display: flex;\\n justify-content: space-between;\\n margin-top: 1em;\\n}\\nform.purchase {\\n width: 672px;\\n}\\napp {\\n display: flex;\\n flex: 1 0;\\n height: 100%;\\n flex-direction: column;\\n}\\nbutton.loop {\\n font-size: 32px;\\n line-height: 1rem;\\n}\\naside.meter-picker {\\n margin-left: 8px;\\n}\\naside.meter-picker > button {\\n border-radius: 0;\\n}\\naside.meter-picker > button:first-child {\\n border-top-left-radius: 4px;\\n border-bottom-left-radius: 4px;\\n}\\naside.meter-picker > button:last-child {\\n border-top-right-radius: 4px;\\n border-bottom-right-radius: 4px;\\n}\\naside.note-control > section {\\n display: flex;\\n margin-left: 1rem;\\n}\\naside.note-control > section > label {\\n display: flex;\\n align-items: center;\\n margin-right: 4px;\\n}\\naside.note-control > section > button {\\n flex: 0 0 auto;\\n font-size: 20px;\\n line-height: 1rem;\\n width: 36px;\\n border-radius: 0;\\n}\\naside.note-control > section > button.triplet {\\n font-size: 16px;\\n}\\naside.note-control > section > button:nth-child(n + 3) {\\n border-left: 0;\\n}\\naside.note-control > section > button:nth-child(2) {\\n border-top-left-radius: 4px;\\n border-bottom-left-radius: 4px;\\n}\\naside.note-control > section.snap > button:nth-child(5) {\\n border-top-right-radius: 4px;\\n border-bottom-right-radius: 4px;\\n}\\naside.note-control > section.snap > input {\\n margin-left: 5px;\\n width: 60px;\\n}\\naside.note-control > section.accidental > button:nth-child(4) {\\n border-top-right-radius: 4px;\\n border-bottom-right-radius: 4px;\\n}\\nsection.persistence {\\n display: flex;\\n flex-direction: column;\\n padding: 1rem;\\n width: 480px;\\n}\\nsection.persistence > button {\\n margin-top: 8px;\\n width: 100%;\\n}\\nsection.persistence > button:first-child {\\n margin-top: 0;\\n}\\nabout > actions > button,\\nabout > actions a.button {\\n width: 100%;\\n margin-top: 8px;\\n}\\naside {\\n display: flex;\\n}\\naside > label {\\n display: flex;\\n align-items: center;\\n margin-right: 4px;\\n margin-left: 1rem;\\n}\\naside.stereo-analyser > canvas {\\n border: 1px solid #673ab7;\\n border-radius: 4px;\\n}\\naside.fx-picker {\\n display: flex;\\n}\\naside.fx-picker > button {\\n background-size: 100%;\\n border: 1px solid #673ab7;\\n border-radius: 4px;\\n height: 36px;\\n padding: 0;\\n margin-left: 2px;\\n width: 36px;\\n}\\naside.actions {\\n background-color: #fff;\\n border-top: 2px solid #673ab7;\\n display: flex;\\n flex: 0 0 auto;\\n flex-direction: column;\\n padding: 4px 4px 0px 4px;\\n width: 100%;\\n}\\naside.actions > section:nth-child(2) > button,\\naside.actions > section:nth-child(2) > a.button {\\n padding: 15px 8px;\\n}\\naside.actions > section {\\n display: flex;\\n}\\naside.actions > section:last-child {\\n margin-top: 8px;\\n}\\naside.actions > section.buttons > * {\\n margin-bottom: 6px;\\n}\\naside.actions > section.buttons > *:nth-child(n + 2) {\\n margin-left: 4px;\\n}\\naside.actions > section.buttons > form {\\n display: flex;\\n}\\naside.actions > section.buttons > button,\\naside.actions > section.buttons > a.button {\\n flex: 0 0 auto;\\n}\\naside.actions > section.buttons > label {\\n align-items: center;\\n border: 1px solid #673ab7;\\n border-radius: 4px;\\n box-shadow: 1px 2px 0px #673ab7;\\n color: #673ab7;\\n display: flex;\\n flex-direction: column;\\n padding: 5px 8px 4px;\\n white-space: nowrap;\\n}\\naside.actions > section.buttons > label > input {\\n padding: 2px 0 0;\\n text-align: center;\\n width: 60px;\\n}\\naside.actions > section.buttons > label > h2 {\\n display: block;\\n font-size: 1em;\\n font-weight: normal;\\n margin: 0 0 4px;\\n}\\naside.actions > section.buttons > .right {\\n margin-left: auto;\\n}\\nactions > label {\\n background-color: #fff;\\n border: 1px solid #673ab7;\\n border-radius: 4px;\\n box-shadow: 1px 2px 0px #673ab7;\\n color: #673ab7;\\n cursor: pointer;\\n font: inherit;\\n line-height: 1em;\\n padding: 9px 16px;\\n}\\nactions > label:nth-child(n + 2) {\\n margin-left: 4px;\\n}\\na.button {\\n align-items: center;\\n display: inline-flex;\\n justify-content: center;\\n text-align: center;\\n text-decoration: none;\\n}\\na.button > :nth-child(2) {\\n margin-left: 5px;\\n}\\nbutton,\\na.button {\\n background-color: #fff;\\n border: 1px solid #673ab7;\\n border-radius: 4px;\\n box-shadow: 1px 2px 0px #673ab7;\\n color: #673ab7;\\n cursor: pointer;\\n font-size: inherit;\\n line-height: 1em;\\n padding: 9px 8px;\\n white-space: nowrap;\\n}\\nbutton.full,\\na.button.full {\\n width: 100%;\\n}\\nbutton:focus,\\na.button:focus {\\n background-color: #ffc107;\\n color: rgba(0,0,0,0.69);\\n outline-offset: 4px;\\n}\\nbutton:active,\\na.button:active,\\nbutton.active,\\na.button.active {\\n background-color: #673ab7;\\n border: 1px solid #673ab7;\\n color: #fff;\\n box-shadow: 1px 2px 0px #241440 inset;\\n}\\nbutton:disabled,\\na.button:disabled {\\n background-color: #eee;\\n border-color: #4e4e4e;\\n box-shadow: 1px 2px 0px #4e4e4e;\\n color: #4e4e4e;\\n cursor: default;\\n}\\ntools,\\npatterns {\\n background-color: #fff;\\n border-bottom: 2px solid #673ab7;\\n display: flex;\\n width: 100%;\\n}\\ntools > *,\\npatterns > * {\\n background-repeat: no-repeat;\\n background-position: 50% 50%;\\n border-right: 1px solid #673ab7;\\n cursor: pointer;\\n display: block;\\n width: 49px;\\n height: 48px;\\n}\\ntools > *:hover,\\npatterns > *:hover {\\n background-color: #ffe0b2;\\n}\\ntools > *.active,\\npatterns > *.active {\\n background-color: #ffc107;\\n}\\ntools > tool.eraser,\\npatterns > tool.eraser {\\n order: 2;\\n background-image: url(\\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAAIdJREFUeJzNUsERwCAIw15n031wDt0Hl0s/9VoF9NnmZzRBCERfI2zusdOtDABmopRGVoRCrdviADNMiADM6L873Mql2NYiw3E2WItzVi2dSuw8JBHNvQyegcU4vmjNFesWZrHFTSlYQ/RhRDgatKZFnXPy7zMIoVaYa3fH5i3PTHira4r/gQv1W1E4p9FksQAAAABJRU5ErkJggg==\\\");\\n}\\ntools > .selection,\\npatterns > .selection {\\n order: 2;\\n background-image: url(\\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAiUlEQVRYR+1XQQrAIAyr/3/0RLAyqoUdzNJDvFnEpkkM2MzssX21Qw1SGo0igHfNgfiZ6/sI4HqDSVs6wGlaCNXZpQ7gN80jkIxyNAtLkjIA0BOn99O0d0RlJIjvFC3JZkJ0Q3kgMqAcWIwoB8oEkXKAHsV0APIAXQI6AHkA9vf78jUbZ+gAaB7opmM7IWnG3nUAAAAASUVORK5CYII=\\\");\\n}\\ntools > * {\\n -ms-interpolation-mode: nearest-neighbor;\\n image-rendering: crisp-edges;\\n image-rendering: pixelated;\\n}\\npatterns {\\n margin-top: -1px;\\n}\\npatterns:empty {\\n display: none;\\n}\\npatterns > pattern {\\n display: flex;\\n padding: 0 8px;\\n align-items: center;\\n width: 64px;\\n overflow: hidden;\\n}\\npatterns > pattern > preview {\\n position: relative;\\n transform: scale(0.0625, 0.0625);\\n top: -14px;\\n}\\npatterns > pattern > preview > * {\\n transform: scale(4);\\n}\\npre.position {\\n background-color: rgba(255,255,255,0.938);\\n border: 1px solid #000;\\n box-shadow: 1px 1px 0 0 rgba(0,0,0,0.5);\\n padding: 2px 6px 2px 4px;\\n font-family: inherit;\\n pointer-events: none;\\n padding: 4px;\\n position: absolute;\\n left: 1rem;\\n top: calc(45px + 1rem);\\n}\\npre.position:empty {\\n display: none;\\n}\\npre.debug:empty {\\n display: none;\\n}\\n@media only screen and (max-width: 768px) {\\n aside.actions {\\n border-top: none;\\n padding-top: 0;\\n }\\n aside.actions > section:first-child {\\n display: none;\\n }\\n aside.actions > section:nth-child(2) {\\n margin-top: 0;\\n }\\n button > span.description {\\n display: none;\\n }\\n tools,\\n patterns {\\n order: 2;\\n border: none;\\n width: 49px;\\n height: 48px;\\n }\\n tools > *,\\n patterns > * {\\n border-top: 1px solid #673ab7;\\n display: none;\\n }\\n tools tool.active,\\n patterns tool.active {\\n display: block;\\n }\\n tools pattern.active,\\n patterns pattern.active {\\n display: flex;\\n }\\n tools.open,\\n patterns.open {\\n background-color: #673ab7;\\n display: grid;\\n grid-gap: 1px;\\n position: absolute;\\n width: 100%;\\n height: 100%;\\n grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;\\n z-index: 1;\\n }\\n tools.open > *,\\n patterns.open > * {\\n background-color: #fff;\\n background-size: 100%;\\n border: none;\\n display: block;\\n image-rendering: pixelated;\\n height: 100%;\\n width: 100%;\\n }\\n tools.open > *:hover,\\n patterns.open > *:hover {\\n background-color: #ffe0b2;\\n }\\n}\\n@-moz-keyframes note-active {\\n from {\\n background-color: rgba(0,0,0,0);\\n }\\n to {\\n background-color: rgba(255,0,255,0);\\n }\\n}\\n@-webkit-keyframes note-active {\\n from {\\n background-color: rgba(0,0,0,0);\\n }\\n to {\\n background-color: rgba(255,0,255,0);\\n }\\n}\\n@-o-keyframes note-active {\\n from {\\n background-color: rgba(0,0,0,0);\\n }\\n to {\\n background-color: rgba(255,0,255,0);\\n }\\n}\\n@keyframes note-active {\\n from {\\n background-color: rgba(0,0,0,0);\\n }\\n to {\\n background-color: rgba(255,0,255,0);\\n }\\n}\\n\";"
},
"templates/about": {
"content": "module.exports = system.ui.Jadelet.exec([\"about\",{},[[\"h1\",{},[\"About\"]],[\"p\",{},[\"ProTip™ hold Shift to sharp, Ctrl to flat\"]],[\"p\",{},[\"By\\n\",[\"a\",{\"href\":\"https://danielx.net\"},[\"Daniel X. Moore\"]],\" creator of\\n\",[\"a\",{\"href\":\"https://whimsy.space\"},[\"Whimsy.Space\"]]]]]]);"
},
"templates/actions": {
"content": "module.exports = system.ui.Jadelet.exec([\"aside\",{\"class\":[\"actions\"]},[[\"section\",{},[[\"button\",{\"class\":[\"loop\",{\"bind\":\"loopButtonClass\"}],\"click\":{\"bind\":\"toggleLoop\"}},[\"𝄇\"]],{\"bind\":\"meterPickerElement\"},{\"bind\":\"noteControlElement\"},{\"bind\":\"fxPickerElement\"},{\"bind\":\"stereoAnalyserElement\"}]],[\"section\",{\"class\":[\"buttons\"]},[[\"button\",{\"class\":[\"play\",{\"bind\":\"playButtonClass\"}],\"click\":{\"bind\":\"play\"},\"title\":\"Play\"},[[\"span\",{\"class\":[\"icon\"]},[\"▶️\"]],\"\\n\",[\"span\",{\"class\":[\"description\"]},[\"Play\"]]]],[\"button\",{\"click\":{\"bind\":\"rewind\"},\"title\":\"Rewind\"},[[\"span\",{\"class\":[\"icon\"]},[\"⏪\"]],\"\\n\",[\"span\",{\"class\":[\"description\"]},[\"Go to Start\"]]]],[\"label\",{},[[\"h2\",{},[\"Tempo\"]],[\"input\",{\"value\":{\"bind\":\"tempo\"},\"keydown\":{\"bind\":\"inputHotkeys\"},\"type\":\"number\",\"min\":\"1\",\"step\":\"1\"},[]]]],[\"label\",{},[[\"h2\",{},[\"Length\"]],[\"input\",{\"value\":{\"bind\":\"length\"},\"keydown\":{\"bind\":\"inputHotkeys\"},\"type\":\"number\",\"min\":\"4\",\"step\":\"4\"},[]]]],[\"button\",{\"class\":[{\"bind\":\"hiddenUnlessPurchased\"}],\"click\":{\"bind\":\"showArranger\"}},[[\"span\",{\"class\":[\"icon\"]},[\"📑\"]],\"\\n\",[\"span\",{\"class\":[\"description\"]},[\"Sections\"]]]],[\"button\",{\"click\":{\"bind\":\"undo\"},\"disabled\":{\"bind\":\"undoDisabled\"}},[[\"span\",{\"class\":[\"icon\"]},[\"↩️\"]],\"\\n\",[\"span\",{\"class\":[\"description\"]},[\"Undo\"]]]],[\"button\",{\"click\":{\"bind\":\"redo\"},\"disabled\":{\"bind\":\"redoDisabled\"}},[[\"span\",{\"class\":[\"icon\"]},[\"↪️\"]],\"\\n\",[\"span\",{\"class\":[\"description\"]},[\"Redo\"]]]],[\"button\",{\"click\":{\"bind\":\"clear\"},\"disabled\":{\"bind\":\"clearDisabled\"}},[[\"span\",{\"class\":[\"icon\"]},[\"💣\"]],\"\\n\",[\"span\",{\"class\":[\"description\"]},[\"Clear\"]]]],[\"button\",{\"click\":{\"bind\":\"showSpriteConfig\"}},[[\"span\",{\"class\":[\"icon\"]},[\"⚙️\"]],\"\\n\",[\"span\",{\"class\":[\"description\"]},[\"Settings\"]]]],[\"button\",{\"click\":{\"bind\":\"showPersistenceModal\"}},[[\"span\",{\"class\":[\"icon\"]},[\"💾\"]],\"\\n\",[\"span\",{\"class\":[\"description\"]},[\"Save/Load\"]]]],[\"button\",{\"class\":[\"right\",{\"bind\":\"hiddenIfPurchased\"}],\"click\":{\"bind\":\"purchase\"}},[\"💸 Buy Now! 💸\"]],[\"button\",{\"click\":{\"bind\":\"about\"}},[[\"span\",{\"class\":[\"icon\"]},[\"📚\"]],\"\\n\",[\"span\",{\"class\":[\"description\"]},[\"About\"]]]]]]]]);"
},
"templates/app": {
"content": "module.exports = system.ui.Jadelet.exec([\"app\",{},[{\"bind\":\"toolsElement\"},{\"bind\":\"patternsElement\"},{\"bind\":\"staffElement\"},{\"bind\":\"actionsElement\"},[\"pre\",{\"class\":[\"position\"]},[{\"bind\":\"notePosition\"}]]]]);"
},
"templates/button": {
"content": "module.exports = system.ui.Jadelet.exec([\"button\",{\"click\":{\"bind\":\"click\"},\"class\":[{\"bind\":\"class\"}]},[{\"bind\":\"text\"}]]);"
},
"templates/buy-now": {
"content": "module.exports = system.ui.Jadelet.exec([\"section\",{\"class\":[\"purchase\"]},[[\"iframe\",{\"src\":\"https://store.steampowered.com/widget/1208550/?t=If%20you%20enjoy%20Paint%20Composer%20please%20purchase%20the%20full%20version%21%20Features%20include%20multi-section%20arrangements%2C%20key%20signatures%2C%20and%20more%21%20Your%20support%20truly%20matters%20to%20me%20as%20an%20independent%20creator.%20The%20game%20is%20in%20active%20development%20so%20keep%20checking%20back%20for%20the%20latest%20updates%2C%20thank%20you%21\",\"frameborder\":\"0\",\"width\":\"646\",\"height\":\"190\"},[]]]]);"
},
"templates/exit-button": {
"content": "module.exports = system.ui.Jadelet.exec([\"button\",{\"click\":{\"bind\":\"click\"}},[{\"bind\":\"text\"}]]);"
},
"templates/export": {
"content": "module.exports = system.ui.Jadelet.exec([\"form\",{\"submit\":{\"bind\":\"submit\"}},[[\"h2\",{},[{\"bind\":\"title\"}]],[\"label\",{},[[\"h3\",{},[\"File Name\"]],[\"input\",{\"value\":{\"bind\":\"name\"}},[]]]],[\"label\",{},[[\"h3\",{\"class\":[\"inline\"]},[\"Format\"]],[\"select\",{\"value\":{\"bind\":\"selectedType\"}},[{\"bind\":\"formatOptionElements\"}]]]],[\"actions\",{},[[\"button\",{\"click\":{\"bind\":\"cancel\"}},[\"Cancel\"]],[\"button\",{},[\"Export!\"]]]]]]);"
},
"templates/help": {
"content": "module.exports = system.ui.Jadelet.exec([\"about\",{},[{\"bind\":\"hotkeysTableElement\"}]]);"
},
"templates/note-control": {
"content": "module.exports = system.ui.Jadelet.exec([\"aside\",{\"class\":[\"note-control\"]},[[\"section\",{\"class\":[\"accidental\"]},[[\"label\",{},[\"Sharp / Flat\"]],[\"button\",{\"click\":{\"bind\":\"natural\"},\"class\":[{\"bind\":\"naturalActive\"}]},[\"♮\"]],[\"button\",{\"click\":{\"bind\":\"sharp\"},\"class\":[{\"bind\":\"sharpActive\"}]},[\"♯\"]],[\"button\",{\"click\":{\"bind\":\"flat\"},\"class\":[{\"bind\":\"flatActive\"}]},[\"♭\"]]]],[\"section\",{\"class\":[\"snap\"]},[[\"label\",{},[\"Snap\"]],[\"button\",{\"click\":{\"bind\":\"quarter\"},\"class\":[{\"bind\":\"quarterActive\"}]},[\"♩\"]],[\"button\",{\"click\":{\"bind\":\"eight\"},\"class\":[{\"bind\":\"eigthActive\"}]},[\"♪\"]],[\"button\",{\"click\":{\"bind\":\"sixteenth\"},\"class\":[{\"bind\":\"sixteenthActive\"}]},[\"♬\"]],[\"button\",{\"class\":[\"triplet\",{\"bind\":\"tripletActive\"}],\"click\":{\"bind\":\"triplet\"}},[\"3\"]],[\"input\",{\"value\":{\"bind\":\"quantizeInput\"}},[]]]]]]);"
},
"templates/note": {
"content": "module.exports = system.ui.Jadelet.exec([\"note\",{\"style\":[{\"bind\":\"style\"}],\"class\":[{\"bind\":\"note\"},{\"bind\":\"accidental\"},{\"bind\":\"instrument\"}]},[]]);"
},
"templates/option": {
"content": "module.exports = system.ui.Jadelet.exec([\"option\",{\"value\":{\"bind\":\"value\"}},[{\"bind\":\"text\"}]]);"
},
"templates/pattern": {
"content": "module.exports = system.ui.Jadelet.exec([\"pattern\",{\"class\":[{\"bind\":\"class\"}],\"click\":{\"bind\":\"click\"}},[[\"preview\",{},[{\"bind\":\"notes\"}]]]]);"
},
"templates/patterns": {
"content": "module.exports = system.ui.Jadelet.exec([\"patterns\",{},[{\"bind\":\"patterns\"}]]);"
},
"templates/publish": {
"content": "module.exports = system.ui.Jadelet.exec([\"div\",{\"class\":[\"publish\"]},[[\"h1\",{},[\"Published!\"]],[\"p\",{\"class\":[\"status\"]},[{\"bind\":\"status\"}]],[\"pre\",{},[{\"bind\":\"value\"}]],[\"actions\",{},[[\"button\",{\"click\":{\"bind\":\"copy\"}},[\"Copy Link\"]],[\"button\",{\"click\":{\"bind\":\"done\"}},[\"Done\"]]]]]]);"
},
"templates/sample-tool": {
"content": "module.exports = system.ui.Jadelet.exec([\"tool\",{\"class\":[{\"bind\":\"index\"},{\"bind\":\"active\"}],\"click\":{\"bind\":\"click\"}},[]]);"
},
"templates/staff": {
"content": "module.exports = system.ui.Jadelet.exec([\"viewport\",{\"contextmenu\":{\"bind\":\"contextmenu\"},\"dragstart\":{\"bind\":\"dragstart\"},\"scroll\":{\"bind\":\"scroll\"},\"mousedown\":{\"bind\":\"mousedown\"},\"mousemove\":{\"bind\":\"mousemove\"},\"mouseup\":{\"bind\":\"mouseup\"},\"style\":[{\"bind\":\"fxStyle\"}]},[[\"staff\",{},[[\"lines\",{},[[\"line\",{},[]],[\"line\",{},[]],[\"line\",{},[]],[\"line\",{},[]],[\"line\",{},[]]]],[\"lines\",{},[[\"line\",{},[]],[\"line\",{},[]],[\"line\",{},[]],[\"line\",{},[]],[\"line\",{},[]]]],[\"z-meter\",{},[[\"div\",{\"class\":[\"repeat\",{\"bind\":\"repeatClass\"}]},[]],[\"div\",{\"class\":[\"repeat\",\"bass\",{\"bind\":\"repeatClass\"}]},[]]]],[\"notes\",{},[]],[\"selection\",{\"class\":[{\"bind\":\"selectionClass\"}]},[[\"button\",{\"class\":[\"up\"],\"click\":{\"bind\":\"selectionMoveUp\"}},[\"🡅\"]],[\"button\",{\"class\":[\"down\"],\"click\":{\"bind\":\"selectionMoveDown\"}},[\"🡇\"]],[\"button\",{\"class\":[\"left\"],\"click\":{\"bind\":\"selectionMoveLeft\"}},[\"🡄\"]],[\"button\",{\"class\":[\"right\"],\"click\":{\"bind\":\"selectionMoveRight\"}},[\"🡆\"]],[\"actions\",{},[[\"button\",{\"click\":{\"bind\":\"selectionCopy\"}},[\"Copy\"]],[\"button\",{\"click\":{\"bind\":\"selectionCut\"}},[\"Cut\"]],[\"button\",{\"click\":{\"bind\":\"selectionDelete\"}},[\"Delete\"]],[\"button\",{\"click\":{\"bind\":\"selectionReset\"}},[\"Cancel\"]]]]]],[\"pattern-preview\",{},[]],[\"playhead\",{},[]],[\"playhead\",{\"class\":[\"buffer-start\"]},[]],[\"playhead\",{\"class\":[\"buffer-end\"]},[]],[\"img\",{\"src\":\"assets/treble-clef.svg\"},[]],[\"img\",{\"src\":\"assets/bass-clef.svg\"},[]]]]]]);"
},
"templates/stripe/form": {
"content": "module.exports = system.ui.Jadelet.exec([\"form\",{\"action\":\"/charge\",\"method\":\"post\",\"submit\":{\"bind\":\"submit\"}},[[\"input\",{\"type\":\"hidden\",\"name\":\"stripeToken\",\"value\":{\"bind\":\"tokenId\"}},[]]]]);"
},
"templates/tools": {
"content": "module.exports = system.ui.Jadelet.exec([\"tools\",{\"class\":[{\"bind\":\"toolsClass\"}],\"click\":{\"bind\":\"toggleToolsOpen\"}},[{\"bind\":\"sampleTools\"},[\"tool\",{\"class\":[\"eraser\",{\"bind\":\"eraserActive\"}],\"click\":{\"bind\":\"eraser\"}},[]],[\"tool\",{\"class\":[\"selection\",{\"bind\":\"selectionActive\"}],\"click\":{\"bind\":\"selection\"}},[]]]]);"
},
"test/sample": {
"content": "var Sample;\n\nSample = require(\"../sample\");\n\ndescribe(\"Sample\", function() {\n return it(\"should be able to load the default sample pack\", function(done) {\n Sample.loadPack().then(function(samples) {\n return done();\n })[\"catch\"](done);\n });\n});\n"
},
"tools": {
"content": "\n/*\nTools\n\nBasic idea is that there are various tools (instrument, eraser, selection, pattern palette, etc.)\n\nEach tool can respond to down, move, up, actions in the music staff.\n\nStatus: Active\n */\nvar Sample, context, emptyElement, noteName, patternSampleNotes, quantize, rangeToText, selectionToRange, staffNoteToPitch, tools, _ref;\n\n_ref = require(\"./lib/util\"), emptyElement = _ref.emptyElement, noteName = _ref.noteName, patternSampleNotes = _ref.patternSampleNotes, quantize = _ref.quantize, staffNoteToPitch = _ref.staffNoteToPitch;\n\ncontext = require(\"./lib/audio-context\");\n\nSample = require(\"./sample\");\n\ntools = [\n {\n down: function(self, beatNote, e, staffPos) {\n var accidental, beat, instrument, keySignature, section, staffNote;\n beat = beatNote[0], staffNote = beatNote[1], accidental = beatNote[2];\n if (e.which === 3) {\n e.preventDefault();\n tools[1].down(self, [beat, staffNote], e, staffPos);\n return;\n }\n if (e.target.tagName === \"VIEWPORT\" || !self.beatNoteInRange(beatNote)) {\n self.setPlayHead(quantize(beat, 1));\n return;\n }\n instrument = self.activeInstrument();\n if (beat >= self.length()) {\n return;\n }\n self.addNote([beat, staffNote, accidental, instrument]);\n context.resume();\n section = self.sectionAt(beat)[0];\n keySignature = section != null ? section.keySignature() : void 0;\n return self.playNote(instrument, staffNoteToPitch(staffNote, accidental, keySignature));\n },\n move: function() {},\n up: function() {}\n }, {\n down: function(self, _arg, e, _arg1) {\n var beat, staffNote, t, _ref1;\n beat = _arg[0], staffNote = _arg[1];\n t = _arg1.t;\n if (self.removeNote([beat, staffNote], {\n at: t,\n within: 0.25\n })) {\n return self.playBuffer((_ref1 = Sample.fx) != null ? _ref1.eraser : void 0, 1, 0);\n }\n },\n move: function() {},\n up: function() {},\n cursor: \"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAAIdJREFUeJzNUsERwCAIw15n031wDt0Hl0s/9VoF9NnmZzRBCERfI2zusdOtDABmopRGVoRCrdviADNMiADM6L873Mql2NYiw3E2WItzVi2dSuw8JBHNvQyegcU4vmjNFesWZrHFTSlYQ/RhRDgatKZFnXPy7zMIoVaYa3fH5i3PTHira4r/gQv1W1E4p9FksQAAAABJRU5ErkJggg==) 8 8, default\"\n }, {\n down: function(self, beatNote) {\n return self.startSelection(beatNote);\n },\n move: function(self, beatNote, e) {\n return self.modifySelection(beatNote, e);\n },\n up: function(self, beatNote) {\n return self.endSelection(beatNote);\n },\n cursor: \"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAiUlEQVRYR+1XQQrAIAyr/3/0RLAyqoUdzNJDvFnEpkkM2MzssX21Qw1SGo0igHfNgfiZ6/sI4HqDSVs6wGlaCNXZpQ7gN80jkIxyNAtLkjIA0BOn99O0d0RlJIjvFC3JZkJ0Q3kgMqAcWIwoB8oEkXKAHsV0APIAXQI6AHkA9vf78jUbZ+gAaB7opmM7IWnG3nUAAAAASUVORK5CYII=) 0 0, default\"\n }, {\n down: function(self, beatNote) {\n var beat, offset, sampleNotes;\n beat = beatNote[0], offset = beatNote[1];\n if (!self.beatNoteInRange(beatNote)) {\n self.setPlayHead(quantize(beat, 1));\n return;\n }\n sampleNotes = patternSampleNotes(self.activePattern(), offset);\n self.addPattern(beatNote, self.activePattern());\n return sampleNotes.forEach(function(_arg) {\n var i, p;\n i = _arg[0], p = _arg[1];\n return self.playNote(i, p);\n });\n },\n move: function(self, beatNote) {\n var x, y, _ref1;\n _ref1 = self.beatNoteToStaffPosition(beatNote), x = _ref1.x, y = _ref1.y;\n return Object.assign(self.patternPreviewElement.style, {\n left: \"\" + x + \"px\",\n top: \"\" + (y - 240) + \"px\"\n });\n },\n up: function(self, beatNote) {},\n cursor: \"default\"\n }\n];\n\nmodule.exports = function(I, self) {\n var first, hw, selectedNotes, selectionActionHorizontal, selectionActionVertical, selectionRange, selectionStart, updateSelectionElement;\n defaults(I, {\n activeInstrument: 0,\n activePatternIndex: 0,\n activeToolIndex: 0\n });\n self.attrObservable(\"activeInstrument\", \"activePatternIndex\", \"activeToolIndex\");\n hw = {\n x: 194 / 8,\n y: 12\n };\n selectionStart = null;\n selectionRange = null;\n selectionActionVertical = null;\n selectionActionHorizontal = null;\n selectedNotes = null;\n self.extend({\n activeTool: function() {\n return tools[self.activeToolIndex()];\n },\n activePattern: function() {\n if (self.activeToolIndex() !== 3) {\n return null;\n }\n return self.patterns()[self.activePatternIndex()];\n },\n inputHotkeys: function(e) {\n var key;\n if (e.defaultPrevented) {\n return;\n }\n key = e.key;\n switch (key) {\n case \"Enter\":\n e.preventDefault();\n return self.playFromStart();\n case \" \":\n e.preventDefault();\n return self.pause();\n }\n },\n selectionClass: Observable(\"\"),\n startSelection: function(beatNote) {\n self.selectionClass(\"\");\n selectedNotes = null;\n selectionStart = beatNote;\n selectionRange = selectionToRange(selectionStart, selectionStart);\n return updateSelectionElement();\n },\n modifySelection: function(beatNote, e) {\n var clientWidth, start, x, y, _ref1;\n if (!selectionStart) {\n return;\n }\n clientWidth = document.documentElement.clientWidth;\n if (e.pageX > 0.95 * clientWidth) {\n self.autoscrolling(8);\n } else if (e.pageX < 0.05 * clientWidth) {\n self.autoscrolling(-8);\n } else {\n self.autoscrolling(0);\n }\n start = self.beatNoteToStaffPosition(selectionStart);\n _ref1 = self.beatNoteToStaffPosition(beatNote), x = _ref1.x, y = _ref1.y;\n if (x < start.x) {\n selectionActionHorizontal = \"l\";\n } else {\n selectionActionHorizontal = \"r\";\n }\n if (y < start.y) {\n selectionActionVertical = \"t\";\n } else {\n selectionActionVertical = \"b\";\n }\n selectionRange = selectionToRange(selectionStart, beatNote);\n self.notePosition(rangeToText(selectionRange));\n return updateSelectionElement();\n },\n endSelection: function(beatNote) {\n if (!selectionStart) {\n return;\n }\n self.autoscrolling(0);\n selectionStart = null;\n return self.selectionClass(\"set \" + selectionActionVertical + \" \" + selectionActionHorizontal);\n },\n selectionReset: function() {\n selectionRange = null;\n selectionStart = null;\n selectedNotes = null;\n self.selectionClass(\"\");\n return Object.assign(self.selectionElement.style, {\n left: \"-150px\",\n width: 0,\n height: 0\n });\n },\n selectionCopy: function() {\n if (!selectionRange) {\n return;\n }\n self.copyRange(selectionRange);\n return self.selectionReset();\n },\n selectionCut: function() {\n if (!selectionRange) {\n return;\n }\n self.copyRange(selectionRange);\n self.deleteRange(selectionRange);\n return self.selectionReset();\n },\n selectionDelete: function() {\n if (!selectionRange) {\n return;\n }\n self.deleteRange(selectionRange);\n return self.selectionReset();\n },\n _setSelectedNotes: function(notes) {\n return selectedNotes = notes;\n },\n selectionMove: function(beatDelta, staffDelta) {\n if (!selectionRange) {\n return;\n }\n if (selectionStart) {\n return;\n }\n if (!selectedNotes) {\n selectedNotes = self.song().notesWithinRange(selectionRange);\n }\n self.moveNotes(selectedNotes, beatDelta, staffDelta);\n selectionRange[0][0] += beatDelta;\n selectionRange[0][1] += beatDelta;\n selectionRange[1][0] += staffDelta;\n selectionRange[1][1] += staffDelta;\n updateSelectionElement();\n return self.adjustScroll(beatDelta);\n },\n navigateLeft: function() {\n var unit;\n if (selectionRange) {\n unit = self.quantize() || 0.25;\n return self.selectionMove(-unit, 0);\n } else {\n return self.adjustPlayhead(-4, 4);\n }\n },\n navigateRight: function() {\n var unit;\n if (selectionRange) {\n unit = self.quantize() || 0.25;\n return self.selectionMove(unit, 0);\n } else {\n return self.adjustPlayhead(4, 4);\n }\n },\n navigatePageUp: function() {\n return self.adjustPlayhead(-16, 16);\n },\n navigatePageDown: function() {\n return self.adjustPlayhead(16, 16);\n },\n selectionMoveUp: function() {\n return self.selectionMove(0, 1);\n },\n selectionMoveDown: function() {\n return self.selectionMove(0, -1);\n },\n navigateHome: function() {\n return self.setPlayHead(0);\n },\n navigateEnd: function() {\n self.setPlayHead(self.song().length());\n return self.scrollTo(999999);\n }\n });\n self.addHotkey(\"esc\", function() {\n return self.selectionReset();\n });\n self.addHotkey(\"space\", \"pause\");\n self.addHotkey(\"enter\", \"playFromStart\");\n self.addHotkey(\"del\", \"selectionDelete\");\n self.addHotkey(\"c\", \"selectionCopy\");\n self.addHotkey(\"x\", \"selectionCut\");\n self.addHotkey(\"s\", function() {\n return self.activeToolIndex(2);\n });\n self.addHotkey(\"left\", \"navigateLeft\");\n self.addHotkey(\"right\", \"navigateRight\");\n self.addHotkey(\"up\", \"selectionMoveUp\");\n self.addHotkey(\"down\", \"selectionMoveDown\");\n self.addHotkey(\"pageup\", \"navigatePageUp\");\n self.addHotkey(\"pagedown\", \"navigatePageDown\");\n self.addHotkey(\"home\", \"rewind\");\n self.addHotkey(\"end\", \"navigateEnd\");\n self.addHotkey(\"e\", function() {\n return self.activeToolIndex(1);\n });\n self.addHotkey([\"ctrl+z\", \"meta+z\"], \"undo\");\n self.addHotkey([\"ctrl+y\", \"meta+y\"], \"redo\");\n self.addHotkey([\"ctrl+s\", \"meta+s\"], \"saveFile\");\n self.addHotkey([\"ctrl+o\", \"meta+o\"], \"openFile\");\n self.addHotkey([\"ctrl+r\", \"meta+r\"], \"exportAudio\");\n self.addHotkey([\"f1\"], \"about\");\n self.addHotkey(\"?\", \"showHelp\");\n [1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(function(i) {\n var n;\n n = i - 1;\n self.addHotkey(i.toString(), function() {\n self.activeInstrument(n);\n return self.activeToolIndex(0);\n });\n return self.addHotkey(\"shift+\" + i, function() {\n if (self.patterns().length > n) {\n self.activePatternIndex(n);\n return self.activeToolIndex(3);\n }\n });\n });\n self.addHotkey(\"0\", function() {\n self.activeInstrument(9);\n return self.activeToolIndex(0);\n });\n [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(function(i) {\n return self.addHotkey(\"` \" + i, function() {\n var n;\n n = i + 10;\n self.activeInstrument(n);\n return self.activeToolIndex(0);\n });\n });\n first = true;\n Observable(function() {\n var n, toolIndex;\n toolIndex = self.activeToolIndex();\n n = self.activeInstrument();\n if (toolIndex === 0 && (n != null) && !first) {\n setTimeout(function() {\n return self.playNote(n);\n }, 0);\n }\n return first = false;\n });\n Observable(function() {\n var pattern;\n pattern = self.activePattern();\n if (!self.patternPreviewElement) {\n return;\n }\n emptyElement(self.patternPreviewElement);\n if (pattern) {\n return setTimeout(function() {\n patternSampleNotes(self.activePattern()).forEach(function(_arg) {\n var i, p;\n i = _arg[0], p = _arg[1];\n return self.playNote(i, p);\n });\n return pattern.notes().forEach(function(note) {\n return self.patternPreviewElement.appendChild(self.renderNote(note));\n });\n });\n }\n });\n updateSelectionElement = function() {\n var endBeat, endNote, height, startBeat, startNote, width, x, x2, y, y2, _ref1, _ref2, _ref3, _ref4;\n (_ref1 = selectionRange[0], startBeat = _ref1[0], endBeat = _ref1[1]), (_ref2 = selectionRange[1], startNote = _ref2[0], endNote = _ref2[1]);\n _ref3 = self.beatNoteToStaffPosition([startBeat, startNote]), x = _ref3.x, y2 = _ref3.y;\n _ref4 = self.beatNoteToStaffPosition([endBeat, endNote]), x2 = _ref4.x, y = _ref4.y;\n width = x2 - x;\n height = y2 - y;\n return Object.assign(self.selectionElement.style, {\n top: \"\" + (y - hw.y) + \"px\",\n left: \"\" + (x - hw.x) + \"px\",\n width: \"\" + (width + 2 * hw.x) + \"px\",\n height: \"\" + (height + 2 * hw.y) + \"px\"\n });\n };\n return self;\n};\n\nselectionToRange = function(_arg, _arg1) {\n var endBeat, endNote, startBeat, startNote, x;\n startBeat = _arg[0], startNote = _arg[1];\n endBeat = _arg1[0], endNote = _arg1[1];\n if (startBeat > endBeat) {\n x = endBeat;\n endBeat = startBeat;\n startBeat = x;\n }\n if (startNote > endNote) {\n x = endNote;\n endNote = startNote;\n startNote = x;\n }\n return [[startBeat, endBeat], [startNote, endNote]];\n};\n\nrangeToText = function(_arg) {\n var endBeat, endNote, startBeat, startNote, _ref1, _ref2;\n (_ref1 = _arg[0], startBeat = _ref1[0], endBeat = _ref1[1]), (_ref2 = _arg[1], startNote = _ref2[0], endNote = _ref2[1]);\n return \"Start: \" + ((startBeat + 1).toFixed(2)) + \"\\nLength: \" + ((endBeat - startBeat).toFixed(2)) + \"\\nRange: \" + (noteName(staffNoteToPitch(startNote))) + \"-\" + (noteName(staffNoteToPitch(endNote)));\n};\n"
},
"views/note-control": {
"content": "\n/*\nNote Control handles quantization, accidentals.\n\nLater will handle duration, dynamics as well.\n\nViews connect the UI to the model.\n */\nvar Observable, Template;\n\nObservable = system.ui.Observable;\n\nTemplate = require(\"../templates/note-control\");\n\nmodule.exports = function(model) {\n var self, tripletMode;\n if (model == null) {\n model = {};\n }\n if (model.accidentalModifier == null) {\n model.accidentalModifier = Observable(0);\n }\n if (model.quantize == null) {\n model.quantize = Observable(1 / 4);\n }\n tripletMode = Observable(false);\n self = {\n accidentalModifier: model.accidentalModifier,\n quantize: model.quantize,\n natural: function() {\n return self.accidentalModifier(0);\n },\n sharp: function() {\n return self.accidentalModifier(1);\n },\n flat: function() {\n return self.accidentalModifier(-1);\n },\n naturalActive: function() {\n if (self.accidentalModifier() === 0) {\n return \"active\";\n }\n },\n sharpActive: function() {\n if (self.accidentalModifier() === 1) {\n return \"active\";\n }\n },\n flatActive: function() {\n if (self.accidentalModifier() === -1) {\n return \"active\";\n }\n },\n quarter: function() {\n if (tripletMode()) {\n return self.quantize(2 / 3);\n } else {\n return self.quantize(1);\n }\n },\n eight: function() {\n if (tripletMode()) {\n return self.quantize(1 / 3);\n } else {\n return self.quantize(1 / 2);\n }\n },\n sixteenth: function() {\n if (tripletMode()) {\n return self.quantize(1 / 6);\n } else {\n return self.quantize(1 / 4);\n }\n },\n triplet: function() {\n var q;\n q = self.quantize();\n if (tripletMode()) {\n tripletMode(false);\n return self.quantize(q * 3 / 2);\n } else {\n tripletMode(true);\n return self.quantize(q * 2 / 3);\n }\n },\n quarterActive: function() {\n var q;\n q = self.quantize();\n if (q === 1 || q === 2 / 3) {\n return \"active\";\n }\n },\n eigthActive: function() {\n var q;\n q = self.quantize();\n if (q === 1 / 2 || q === 1 / 3) {\n return \"active\";\n }\n },\n sixteenthActive: function() {\n var q;\n q = self.quantize();\n if (q === 1 / 4 || q === 1 / 6) {\n return \"active\";\n }\n },\n tripletActive: function() {\n if (tripletMode()) {\n return \"active\";\n }\n },\n quantizeInput: Observable(model.quantize._value),\n element: null\n };\n model.quantize.observe(function(value) {\n return self.quantizeInput(value);\n });\n self.quantizeInput.observe(function(value) {\n var v;\n v = parseFloat(value);\n if (isFinite(v)) {\n return self.quantize(v);\n }\n });\n self.element = Template(self);\n return self;\n};\n"
},
"views/patterns": {
"content": "var PatternPresenter, PatternTemplate, PatternsTemplate;\n\nPatternsTemplate = require(\"../templates/patterns\");\n\nPatternTemplate = require(\"../templates/pattern\");\n\nPatternPresenter = function(pattern, i) {\n return PatternTemplate({\n \"class\": function() {\n if (player.activePatternIndex() === i && player.activeToolIndex() === 3) {\n return \"active\";\n }\n },\n click: function() {\n player.activeToolIndex(3);\n return player.activePatternIndex(i);\n },\n notes: function() {\n return pattern.notes().filter(function(_arg) {\n var t;\n t = _arg[0];\n return t <= 4;\n }).map(player.renderNote);\n }\n });\n};\n\nmodule.exports = function(player) {\n return {\n element: PatternsTemplate({\n patterns: function() {\n return player.patterns.map(PatternPresenter);\n }\n })\n };\n};\n"
},
"views/tools": {
"content": "var Modal, Observable, SampleToolTemplate, ToolsTemplate, _ref;\n\nToolsTemplate = require(\"../templates/tools\");\n\nSampleToolTemplate = require(\"../templates/sample-tool\");\n\n_ref = system.ui, Observable = _ref.Observable, Modal = _ref.Modal;\n\nmodule.exports = function(editor) {\n var self;\n self = {\n toggleToolsOpen: function() {\n if (self.toolsClass() === \"open\") {\n return self.toolsClass(\"\");\n } else {\n return self.toolsClass(\"open\");\n }\n },\n toolsClass: Observable(\"\"),\n eraser: function() {\n return editor.activeToolIndex(1);\n },\n eraserActive: function() {\n if (editor.activeToolIndex() === 1) {\n return \"active\";\n }\n },\n selection: function() {\n return editor.activeToolIndex(2);\n },\n selectionActive: function() {\n if (editor.activeToolIndex() === 2) {\n return \"active\";\n }\n },\n sampleTools: function() {\n return editor.samples.map(function(sample, i) {\n return SampleToolTemplate({\n index: \"i\" + i,\n active: function() {\n if (editor.activeInstrument() === i && editor.activeToolIndex() === 0) {\n return \"active\";\n }\n },\n click: function() {\n editor.activeInstrument(i);\n return editor.activeToolIndex(0);\n }\n });\n });\n },\n element: null\n };\n self.element = ToolsTemplate(self);\n return self;\n};\n"
},
"workspaces/size": {
"content": "var items, pre;\n\nitems = Object.keys(PACKAGE.source).map(function(name) {\n return [name, PACKAGE.source[name].content.length];\n}).sort(function(a, b) {\n return b[1] - a[1];\n}).map(function(_arg) {\n var name, size;\n name = _arg[0], size = _arg[1];\n return size.toString().padStart(10) + \" \" + name;\n}).join(\"\\n\");\n\npre = document.createElement(\"pre\");\n\npre.innerText = items;\n\npre.style.overflow = \"auto\";\n\ndocument.body.appendChild(pre);\n"
},
"lbi/fx": {
"content": "\n"
},
"lib/fx": {
"content": "\n/*\nFX\n\nAn audio FX output network. Currently experimenting with a fixed network\nwith some params.\n\nStatus: WIP\n */\nvar attachLFO, chorus, compressor, cygnus, defaultPanner, flanger, limiter, phaser, pingPongDelay, slapback, stereoAnalyser, sweepingPan, water;\n\nattachLFO = function(node, attribute, _arg) {\n var amplitude, context, frequency, gain, lfo;\n frequency = _arg.frequency, amplitude = _arg.amplitude;\n context = node.context;\n lfo = context.createOscillator();\n lfo.frequency.value = frequency;\n lfo.start();\n gain = context.createGain();\n gain.gain.value = amplitude;\n return gain.connect(node[attribute]);\n};\n\n\n/*\ninput -> delay -> gain -> destination\n -> destination\n */\n\nslapback = function(destination) {\n var context, d1, g1, input;\n context = destination.context;\n d1 = context.createDelay(0.1);\n d1.delayTime.value = 0.035;\n g1 = context.createGain();\n g1.gain.value = 0.42;\n d1.connect(g1);\n g1.connect(destination);\n input = context.createGain();\n input.gain.value = 0.75;\n input.connect(destination);\n input.connect(d1);\n return input;\n};\n\n\n/* -> feedback \ninput -> d1 -> g1 -> f1 -> destination\n -> d2 /\n */\n\ncygnus = function(destination) {\n var context, d1, d2, f1, feedback, g1, input;\n context = destination.context;\n d1 = context.createDelay(0.1);\n d1.delayTime.value = 0.069;\n d2 = context.createDelay(0.5);\n d2.delayTime.value = 0.171;\n attachLFO(d2, \"delayTime\", {\n frequency: 1.3,\n amplitude: 0.1\n });\n g1 = context.createGain();\n g1.gain.value = 0.3;\n feedback = context.createGain();\n feedback.gain.value = 0.75;\n d1.connect(g1);\n d2.connect(g1);\n feedback.connect(d1);\n feedback.connect(d2);\n f1 = context.createBiquadFilter();\n f1.type = \"bandpass\";\n f1.frequency.value = 500;\n f1.Q.value = 0.5;\n g1.connect(f1);\n attachLFO(f1, \"frequency\", {\n frequency: 0.44,\n amplitude: 420\n });\n attachLFO(f1, \"Q\", {\n frequency: 0.77,\n amplitude: 0.25\n });\n input = context.createGain();\n input.connect(f1);\n input.connect(d1);\n input.connect(d2);\n f1.connect(feedback);\n f1.connect(destination);\n return input;\n};\n\n\n/*\ninput -> input\n -> wet -> delay1 -> filter -> feedback -> destination\n -> delay2 -> feedback -> destination\n |\n lfo\n\n -> dry -> destination\n */\n\nchorus = function(destination) {\n var context, d1, d2, dryGain, f1, feedback, input, wetGain;\n context = destination.context;\n d1 = context.createDelay(0.1);\n d1.delayTime.value = 0.005;\n f1 = context.createBiquadFilter();\n f1.type = \"highpass\";\n f1.frequency.value = 1000;\n d1.connect(f1);\n d2 = context.createDelay(0.1);\n d2.delayTime.value = 0.007;\n attachLFO(d2, \"delayTime\", {\n frequency: 2.64,\n amplitude: 0.00308\n });\n wetGain = context.createGain();\n wetGain.gain.value = 0.5;\n wetGain.connect(d1);\n wetGain.connect(d2);\n dryGain = context.createGain();\n dryGain.gain.value = 0.5;\n input = context.createGain();\n input.connect(dryGain);\n input.connect(wetGain);\n dryGain.connect(destination);\n feedback = context.createGain();\n feedback.gain.value = 0.15;\n feedback.connect(input);\n f1.connect(destination);\n f1.connect(feedback);\n d2.connect(destination);\n d2.connect(feedback);\n return input;\n};\n\n\n/*\ninput -> wet -> delay -> filter -> destination\n | \\-> feedback -> input\n lfo\n -> dry -> destination\n */\n\nflanger = function(destination) {\n var context, d1, dryGain, f1, feedback, input, wetGain;\n context = destination.context;\n d1 = context.createDelay(0.01);\n d1.delayTime.value = 0.00073;\n f1 = context.createBiquadFilter();\n f1.type = \"lowpass\";\n f1.frequency.value = 1000;\n f1.Q.value = 0.5;\n d1.connect(f1);\n attachLFO(d1, \"delayTime\", {\n frequency: 0.11,\n amplitude: 0.00045\n });\n wetGain = context.createGain();\n wetGain.gain.value = 1;\n wetGain.connect(d1);\n dryGain = context.createGain();\n dryGain.gain.value = 0;\n input = context.createGain();\n input.connect(dryGain);\n dryGain.connect(destination);\n input.connect(wetGain);\n feedback = context.createGain();\n feedback.gain.value = 0.75;\n feedback.connect(input);\n f1.connect(feedback);\n f1.connect(destination);\n return input;\n};\n\nphaser = function(destination) {\n var context, d1, f, feedback, i, input, lfo, lfoGain, prev;\n context = destination.context;\n input = context.createGain();\n input.gain.value = 0.75;\n lfo = context.createOscillator();\n lfo.frequency.value = 0.5;\n lfo.start();\n attachLFO(lfo, \"frequency\", {\n amplitude: 0.25,\n frequency: 0.25\n });\n lfoGain = context.createGain();\n lfoGain.gain.value = 110;\n lfo.connect(lfoGain);\n i = 0;\n prev = input;\n while (i < 8) {\n i++;\n f = context.createBiquadFilter();\n f.type = \"allpass\";\n f.frequency.value = i * 220;\n f.Q.value = 16;\n lfoGain.connect(f.frequency);\n if (prev) {\n prev.connect(f);\n prev = f;\n }\n }\n d1 = context.createDelay(0.01);\n d1.delayTime.value = 0.0073;\n feedback = context.createGain();\n feedback.gain.value = 0.45;\n feedback.connect(d1);\n d1.connect(input);\n prev.connect(feedback);\n prev.connect(destination);\n return input;\n};\n\ncompressor = function(destination) {\n var context, dryGain, input, wetGain;\n context = destination.context;\n compressor = context.createDynamicsCompressor();\n compressor.attack.value = 0.0035;\n compressor.connect(destination);\n wetGain = context.createGain();\n wetGain.gain.value = 0.5;\n wetGain.connect(compressor);\n dryGain = context.createGain();\n dryGain.gain.value = 0.5;\n input = context.createGain();\n input.connect(dryGain);\n dryGain.connect(destination);\n input.connect(wetGain);\n return input;\n};\n\nlimiter = function(destination) {\n var context;\n context = destination.context;\n limiter = context.createDynamicsCompressor();\n limiter.threshold.value = 0.0;\n limiter.knee.value = 0.0;\n limiter.ratio.value = 20.0;\n limiter.attack.value = 0.005;\n limiter.release.value = 0.01;\n limiter.connect(destination);\n return limiter;\n};\n\nsweepingPan = function(destination) {\n var context, lfo, panner;\n context = destination.context;\n panner = context.createStereoPanner();\n lfo = context.createOscillator();\n lfo.frequency.value = 0.25;\n lfo.type = \"sine\";\n lfo.start();\n attachLFO(lfo, \"frequency\", {\n frequency: 0.77,\n amplitude: 0.125\n });\n lfo.connect(panner.pan);\n panner.connect(destination);\n return panner;\n};\n\n\n/*\nStereo Ping Pong Delay\n\nFrom my brief research there seem to be a few different ways people wire up\nthese kind of stereo ping pong delays. My goal is to have it be relatively\nsimple, have the unmodified signal proceed through each channel, and have the\ndelayed signal on the opposite channel.\n\n -> \nL -> DelayL -> GainL FeedbackL\n X\nR -> DelayR -> GainR FeedbackR\n -> \n\nThis diagram is pretty weak ;_;\n */\n\npingPongDelay = function(destination) {\n var context, delayL, delayR, gainL, gainR, merger, output, splitter;\n context = destination.context;\n splitter = context.createChannelSplitter(2);\n merger = context.createChannelMerger(2);\n delayL = context.createDelay();\n delayL.delayTime.value = 0.045;\n delayR = context.createDelay();\n delayR.delayTime.value = 0.024;\n splitter.connect(delayL, 0);\n splitter.connect(delayR, 1);\n gainL = context.createGain();\n gainL.gain.value = 0.4;\n gainR = context.createGain();\n gainR.gain.value = 0.4;\n delayL.connect(gainL);\n delayR.connect(gainR);\n gainR.connect(merger, 0, 0);\n gainL.connect(merger, 0, 1);\n gainL.connect(delayR);\n gainR.connect(delayL);\n splitter.connect(merger, 0, 0);\n splitter.connect(merger, 1, 1);\n output = context.createGain();\n output.gain.value = 0.75;\n output.connect(destination);\n merger.connect(output);\n return splitter;\n};\n\nstereoAnalyser = function(destination) {\n var analyserL, analyserR, context, merger, splitter;\n context = destination.context;\n splitter = context.createChannelSplitter(2);\n merger = context.createChannelMerger(2);\n analyserL = context.createAnalyser();\n analyserR = context.createAnalyser();\n analyserL.smoothingTimeConstant.value = 0;\n analyserR.smoothingTimeConstant.value = 0;\n splitter.connect(analyserL, 0);\n splitter.connect(analyserR, 1);\n analyserL.connect(merger, 0, 0);\n analyserR.connect(merger, 0, 1);\n merger.connect(destination);\n Object.assign(splitter, {\n l: analyserL,\n r: analyserR\n });\n return splitter;\n};\n\ndefaultPanner = function(destination) {\n var context, panner;\n context = destination.context;\n panner = context.createStereoPanner();\n panner.connect(destination);\n return panner;\n};\n\nwater = function(destination) {\n var context, d1, f1, f2, f3, g1, input;\n context = destination.context;\n f1 = context.createBiquadFilter();\n f1.type = \"bandpass\";\n f1.frequency.value = 250;\n f1.Q.value = 0.25;\n f2 = context.createBiquadFilter();\n f2.type = \"peaking\";\n f2.frequency.value = 440 / 8;\n f2.Q.value = 1;\n f2.gain.value = 3;\n f3 = context.createBiquadFilter();\n f3.type = \"lowpass\";\n f3.frequency.value = 197;\n f3.Q.value = 1;\n f1.connect(f2);\n f2.connect(destination);\n f3.connect(destination);\n g1 = context.createGain();\n g1.gain.value = 0.7;\n g1.connect(f3);\n d1 = context.createDelay();\n d1.delayTime.value = 0.21;\n d1.connect(g1);\n input = context.createGain();\n input.connect(d1);\n input.connect(f1);\n return chorus(input);\n};\n\nmodule.exports = {\n choices: [\"purple\", \"cygnus\", \"psy\", \"water\"],\n chorus: chorus,\n compressor: compressor,\n cygnus: cygnus,\n defaultPanner: defaultPanner,\n flanger: flanger,\n limiter: limiter,\n pingPongDelay: pingPongDelay,\n purple: slapback,\n fire: function(destination) {\n return flanger(destination);\n },\n psy: function(destination) {\n return phaser(destination);\n },\n slapback: slapback,\n stereoAnalyser: stereoAnalyser,\n sweepingPan: sweepingPan,\n water: water\n};\n"
},
"views/stereo-analyser": {
"content": "\n/*\nStereo analyser node view\n\nStatus: WIP\n */\nvar FX, Template, drawAnalyserCanvas, stereoAnalyser;\n\nstereoAnalyser = (FX = require(\"../lib/fx\")).stereoAnalyser;\n\nTemplate = require(\"../templates/stereo-analyser\");\n\nmodule.exports = function(_arg) {\n var analyser, destination, leftCanvas, rightCanvas, self;\n destination = _arg.destination;\n analyser = stereoAnalyser(destination);\n leftCanvas = drawAnalyserCanvas(analyser.l);\n rightCanvas = drawAnalyserCanvas(analyser.r);\n self = {\n leftCanvas: leftCanvas,\n rightCanvas: rightCanvas,\n analyserNode: analyser,\n element: null\n };\n self.element = Template(self);\n return self;\n};\n\ndrawAnalyserCanvas = function(analyser) {\n var bufferLength, canvas, canvasCtx, dataArray, draw;\n canvas = document.createElement('canvas');\n canvas.width = 200;\n canvas.height = 34;\n canvasCtx = canvas.getContext('2d');\n canvasCtx.fillStyle = \"rgba(103, 58, 183 ,0.19)\";\n canvasCtx.strokeStyle = \"#673ab7\";\n bufferLength = analyser.frequencyBinCount;\n dataArray = new Uint8Array(bufferLength);\n draw = function() {\n var height, i, sliceWidth, v, width, x, y;\n requestAnimationFrame(draw);\n width = canvas.width, height = canvas.height;\n analyser.getByteTimeDomainData(dataArray);\n canvasCtx.clearRect(0, 0, width, height);\n canvasCtx.fillRect(0, 0, width, height);\n canvasCtx.lineWidth = 2;\n canvasCtx.beginPath();\n sliceWidth = canvas.width * 1.0 / bufferLength;\n x = 0;\n i = 0;\n while (i < bufferLength) {\n v = dataArray[i] / 128.0;\n y = v * canvas.height / 2;\n if (i === 0) {\n canvasCtx.moveTo(x, y);\n } else {\n canvasCtx.lineTo(x, y);\n }\n x += sliceWidth;\n i++;\n }\n canvasCtx.lineTo(width, height / 2);\n return canvasCtx.stroke();\n };\n draw();\n return canvas;\n};\n"
},
"templates/stereo-analyser": {
"content": "module.exports = system.ui.Jadelet.exec([\"aside\",{\"class\":[\"stereo-analyser\"]},[[\"label\",{},[\"L\"]],{\"bind\":\"leftCanvas\"},[\"label\",{},[\"R\"]],{\"bind\":\"rightCanvas\"}]]);"
},
"data/views/stereo-analyser": {
"content": "var destination;\n\ndestination = (new AudioContext).destination;\n\nmodule.exports = {\n destination: destination\n};\n"
},
"views/fx-picker": {
"content": "\n/*\nStereo analyser node view\n\nStatus: WIP\n */\nvar FX, FXPicker, ItemTemplate, Jadelet, Template, chorus, delay, styleFor, _ref;\n\n_ref = FX = require(\"../lib/fx\"), chorus = _ref.chorus, delay = _ref.delay;\n\nTemplate = require(\"../templates/fx-picker\");\n\nJadelet = system.ui.Jadelet;\n\nItemTemplate = Jadelet.exec(\"button(@click @style @title class=@active)\");\n\nstyleFor = function(name) {\n if (name === \"purple\") {\n return {\n backgroundColor: \"#ede7f6\"\n };\n } else {\n return {\n backgroundImage: \"url(/assets/fx/\" + name + \".jpg)\"\n };\n }\n};\n\nmodule.exports = FXPicker = function(_arg) {\n var presetName, self;\n presetName = _arg.presetName;\n self = {\n choices: function() {\n return FX.choices;\n },\n items: function() {\n return this.choices().map(function(name) {\n return ItemTemplate({\n title: name,\n click: function() {\n return presetName(name);\n },\n style: function() {\n return styleFor(name);\n },\n active: function() {\n if (presetName() === name) {\n return \"active\";\n }\n }\n });\n });\n },\n element: null\n };\n self.element = Template(self);\n return self;\n};\n\nFXPicker.styleFor = styleFor;\n"
},
"templates/fx-picker": {
"content": "module.exports = system.ui.Jadelet.exec([\"aside\",{\"class\":[\"fx-picker\"]},[[\"label\",{},[\"FX\"]],{\"bind\":\"items\"}]]);"
},
"data/views/fx-picker": {
"content": "var Observable;\n\nObservable = system.ui.Observable;\n\nmodule.exports = {\n presetName: Observable(\"cygnus\")\n};\n"
},
"data/demo-songs": {
"content": "module.exports = [\n {\n title: \"Dubsgrace\",\n author: \"diamondblaze413\",\n slug: \"api-4IuXACKoqtSke9jTAThC5I_eWc4me53ze-1cri-L8kE\"\n }, {\n title: \"Orinoco Flow\",\n author: \"Enya\",\n slug: \"api-xeSsrUhuXkiWKC2_4pwoaCc80rK1hRX7uLeRM8dOs7g\"\n }, {\n title: \"MeGaLoVania\",\n author: \"Toby Fox (arranged by Jackattack413)\",\n slug: \"50c687fa90400971abb010e741aa78c4\"\n }, {\n title: \"Mushroom Forest\",\n author: \"Junko Tamiya (Little Nemo: The Dream Master NES) (arranged by Daniel X Moore)\",\n slug: \"api-KyDf0uvvBi4xOv-_Ze8-cwrEqHC_Qy_fNqgg984FbN8\"\n }, {\n title: \"Wind Forest\",\n author: \"Joe Hisaishi (arranged by A. E. Moore)\",\n slug: \"api-4G6CcZfyxeWyEjtnBNq_3_D3rWC1AYVYpqoxBpbit08\"\n }, {\n title: \"600 AD\",\n author: \"(arranged by LachrymatoryAgent)\",\n slug: \"13ff6ed6dd7c14fdaf63\"\n }\n];\n"
},
"views/demo-picker": {
"content": "var Jadelet, Modal, RowTemplate, Template, demoSongs, _ref;\n\ndemoSongs = require(\"../data/demo-songs\");\n\n_ref = system.ui, Jadelet = _ref.Jadelet, Modal = _ref.Modal;\n\nTemplate = Jadelet.exec(\"section.demo-picker\\n h2 🎵 Demo Songs 🎵\\n button.close(click=@close) X\\n table\\n thead\\n tr\\n th Title\\n th Author\\n tbody\\n @items\");\n\nRowTemplate = Jadelet.exec(\"tr(@click)\\n td @title\\n td @author\");\n\nmodule.exports = function(player) {\n var self;\n if (player.showDemoSongPicker == null) {\n player.showDemoSongPicker = function() {\n return Modal.show(self.element);\n };\n }\n self = {\n close: function() {\n return Modal.hide();\n },\n items: function() {\n return demoSongs.map(function(_arg) {\n var author, slug, title;\n title = _arg.title, author = _arg.author, slug = _arg.slug;\n return RowTemplate({\n title: title,\n author: author,\n click: function() {\n Modal.hide();\n return player.loadFromSlug(slug);\n }\n });\n });\n },\n element: null\n };\n self.element = Template(self);\n return self;\n};\n"
},
"data/views/demo-picker": {
"content": "module.exports = {\n loadFromSlug: console.log\n};\n"
},
"views/meter-picker": {
"content": "var ButtonTemplate, Jadelet, Template;\n\nJadelet = system.ui.Jadelet;\n\nTemplate = Jadelet.exec(\"aside.meter-picker\\n @buttons\");\n\nButtonTemplate = Jadelet.exec(\"button(@click class=@active) @meter\");\n\nmodule.exports = function(player) {\n var choices, self;\n choices = [\"4/4\", \"3/4\", \"5/4\", \"7/4\"];\n self = {\n buttons: function() {\n return choices.map(function(meter, i) {\n return ButtonTemplate({\n active: function() {\n if (meter === player.timeSignature()) {\n return \"active\";\n }\n },\n click: function() {\n return player.timeSignature(meter);\n },\n meter: meter\n });\n });\n },\n element: null\n };\n self.element = Template(self);\n return self;\n};\n"
},
"const": {
"content": "module.exports = {\n CLEF_OFFSET: 256,\n PIXELS_PER_BEAT: 96\n};\n"
},
"lib/meter-background": {
"content": "\n/*\nGenerates background images that can be used as for the meter in different time\nsignatures.\n\nExports a promise that is fulfilled with an lookup table of background image\nurls indexd by time signatures: \"4/4\", \"3/4\", etc.\n */\nvar PIXELS_PER_BEAT, bgUrls, canvas, context, numerators, pxs;\n\nPIXELS_PER_BEAT = require(\"../const\").PIXELS_PER_BEAT;\n\ncanvas = document.createElement('canvas');\n\ncontext = canvas.getContext('2d');\n\npxs = PIXELS_PER_BEAT;\n\nnumerators = [2, 3, 4, 5, 6, 7];\n\nbgUrls = Promise.all(numerators.map(function(n) {\n var i, urlPromise;\n canvas.height = 1;\n canvas.width = n * pxs;\n context.fillStyle = \"rgba(0, 0, 0, 0.5)\";\n i = 0;\n while (i < n) {\n i++;\n context.fillRect(i * pxs, 0, 1, 1);\n }\n context.fillStyle = \"black\";\n context.fillRect(i * pxs - 2, 0, 2, 1);\n return urlPromise = new Promise(function(resolve, reject) {\n return canvas.toBlob(resolve);\n }).then(function(blob) {\n return (new Image).src = URL.createObjectURL(blob);\n });\n})).then(function(urls) {\n var table;\n table = {};\n numerators.forEach(function(n, i) {\n return table[n] = urls[i];\n });\n return table;\n});\n\nmodule.exports = bgUrls;\n"
},
"workspaces/bench": {
"content": "\n/*\nA test bench for experimenting with the web audio api\n\nGoal: Create nodes and connect them visually to create sweet effects!\nSave and load effects\nRe-use complex effects as simpler components.\n */\nvar Button, Control, DialTemplate, FX, Gain, Observable, Osc, exp1, exp2, exp3, nextId;\n\nObservable = system.ui.Observable;\n\nFX = require(\"../lib/fx\");\n\nButton = function(params) {\n var button;\n if (params == null) {\n params = {};\n }\n button = document.createElement('button');\n return Object.assign(button, params);\n};\n\nnextId = function(context) {\n var lastId, _ref;\n lastId = (_ref = context.lastId) != null ? _ref : 0;\n lastId++;\n context.lastId = lastId;\n return lastId;\n};\n\nOsc = function(context, params) {\n var id, n;\n if (params == null) {\n params = {};\n }\n id = nextId(context);\n n = context.createOscillator();\n Object.keys(params).forEach(function(param) {\n return n[param].value = params[param];\n });\n return n;\n};\n\nGain = function(context, value) {\n var gain;\n gain = context.createGain();\n gain.gain.value = value;\n return gain;\n};\n\nControl = require(\"../views/slider\");\n\nexp1 = function() {\n var c, context, f, g, o, osc1, osc2, t;\n global.context = context = new AudioContext({\n sampleRate: 44100\n });\n f = 440;\n t = 0.0625;\n g = Gain(context, 0);\n osc1 = Osc(context, {\n frequency: f\n });\n osc2 = Osc(context, {\n frequency: f\n });\n osc1.connect(g);\n g.connect(context.destination);\n osc2.connect(context.destination);\n o = Observable(osc1.frequency.value);\n o.observe(function(v) {\n return osc1.frequency.value = v;\n });\n c = Control({\n value: o\n });\n setTimeout(function() {\n var t2;\n t = context.currentTime;\n t2 = t + 1 / (2 * f);\n osc1.start(t);\n osc2.start(t2);\n return g.gain.setValueAtTime(1, t2);\n }, 100);\n return document.body.appendChild(c.element);\n};\n\nexp2 = function() {\n var context, delay, g;\n global.context = context = new AudioContext({\n sampleRate: 44100\n });\n delay = context.createDelay(1);\n delay.delay.value = 0.070;\n g = Gain(context, {\n gain: 0.4\n });\n delay.connect(g);\n return g.connect(context.destination);\n};\n\nexp3 = function() {};\n\ndocument.body.appendChild(Button({\n textContent: \"Exp 1\",\n onclick: exp1\n}));\n\ndocument.body.appendChild(Button({\n textContent: \"Stop\",\n onclick: function() {\n return context.close();\n }\n}));\n\nDialTemplate = require(\"../templates/dial\");\n\ndocument.body.appendChild(DialTemplate());\n"
},
"templates/dial": {
"content": "module.exports = system.ui.Jadelet.exec([\"svg\",{\"xmlns\":\"http://www.w3.org/2000/svg\",\"viewBox\":\"0 0 800 600\"},[[\"circle\",{\"cx\":\"400\",\"cy\":\"300\",\"r\":\"250\",\"stroke-width\":\"20\",\"stroke\":\"#f00\",\"fill\":\"#ff0\"},[]]]]);"
},
"templates/slider": {
"content": "module.exports = system.ui.Jadelet.exec([\"label\",{\"class\":[\"slider\"]},[{\"bind\":\"name\"},\"\\n\",[\"input\",{\"type\":\"number\",\"value\":{\"bind\":\"value\"},\"min\":{\"bind\":\"min\"},\"max\":{\"bind\":\"max\"},\"step\":{\"bind\":\"step\"}},[]],\"\\n\",{\"bind\":\"unit\"}]]);"
},
"views/slider": {
"content": "\n/*\nA simple slider control.\n */\nvar SliderTemplate;\n\nSliderTemplate = require(\"../templates/slider\");\n\nmodule.exports = function(param) {\n var element, self;\n self = Object.assign({}, param, {\n element: null\n });\n self.element = element = SliderTemplate(self);\n element.addEventListener;\n return self;\n};\n"
},
"data/views/slider": {
"content": "var Observable;\n\nObservable = system.ui.Observable;\n\nmodule.exports = {\n name: \"Delay\",\n value: Observable(100),\n min: 0,\n max: 250,\n unit: \"ms\"\n};\n"
},
"templates/persistence": {
"content": "module.exports = system.ui.Jadelet.exec([\"section\",{\"class\":[\"persistence\"]},[[\"button\",{\"click\":{\"bind\":\"showDemoSongPicker\"}},[\"Load a demo song\"]],[\"button\",{\"click\":{\"bind\":\"saveFile\"}},[\"Save to disk\"]],[\"button\",{\"click\":{\"bind\":\"openFile\"}},[\"Load from disk\"]],[\"button\",{\"click\":{\"bind\":\"exportAudio\"}},[\"Export to .wav or .mp3\"]]]]);"
},
"templates/arranger": {
"content": "module.exports = system.ui.Jadelet.exec([\"section\",{\"class\":[\"arranger\"]},[[\"h2\",{},[\"Sections\"]],[\"table\",{},[[\"thead\",{},[[\"tr\",{},[[\"th\",{},[\"Name\"]],[\"th\",{},[\"Time Signature\"]],[\"th\",{},[\"Key Signature\"]],[\"th\",{},[\"Length\"]],[\"th\",{},[\"Tempo\"]],[\"th\",{},[\"FX\"]],[\"th\",{},[\"Action\"]]]]]],[\"tbody\",{},[{\"bind\":\"items\"}]]]],[\"actions\",{},[[\"button\",{\"click\":{\"bind\":\"newSection\"}},[\"+ New Section\"]]]]]]);"
},
"templates/arranger-row": {
"content": "module.exports = system.ui.Jadelet.exec([\"tr\",{},[[\"td\",{},[[\"input\",{\"value\":{\"bind\":\"name\"}},[]]]],[\"td\",{},[[\"select\",{\"value\":{\"bind\":\"timeSignature\"}},[[\"option\",{\"value\":\"4/4\"},[\"4/4\"]],[\"option\",{\"value\":\"3/4\"},[\"3/4\"]],[\"option\",{\"value\":\"5/4\"},[\"5/4\"]],[\"option\",{\"value\":\"7/4\"},[\"7/4\"]]]]]],[\"td\",{},[[\"select\",{\"value\":{\"bind\":\"keySignature\"}},[[\"option\",{\"value\":\"-7\"},[\"C♭\"]],[\"option\",{\"value\":\"-6\"},[\"G♭ / e♭\"]],[\"option\",{\"value\":\"-5\"},[\"D♭ / b♭\"]],[\"option\",{\"value\":\"-4\"},[\"A♭ / f\"]],[\"option\",{\"value\":\"-3\"},[\"E / c\"]],[\"option\",{\"value\":\"-2\"},[\"B / g\"]],[\"option\",{\"value\":\"-1\"},[\"F / d\"]],[\"option\",{\"value\":\"0\"},[\"C / a\"]],[\"option\",{\"value\":\"1\"},[\"G / e\"]],[\"option\",{\"value\":\"2\"},[\"D / b\"]],[\"option\",{\"value\":\"3\"},[\"A / f♯\"]],[\"option\",{\"value\":\"4\"},[\"E / c♯\"]],[\"option\",{\"value\":\"5\"},[\"B / g♯\"]],[\"option\",{\"value\":\"6\"},[\"F♯ / d♯\"]],[\"option\",{\"value\":\"7\"},[\"C♯\"]]]]]],[\"td\",{},[[\"input\",{\"value\":{\"bind\":\"length\"},\"type\":\"number\",\"max\":\"9999\",\"min\":\"1\",\"step\":\"1\"},[]]]],[\"td\",{},[[\"input\",{\"value\":{\"bind\":\"tempo\"},\"type\":\"number\",\"max\":\"900\",\"min\":\"1\",\"step\":\"1\"},[]]]],[\"td\",{},[{\"bind\":\"fxPickerElement\"}]],[\"td\",{},[[\"button\",{\"title\":\"Move Up\",\"click\":{\"bind\":\"moveUp\"},\"disabled\":{\"bind\":\"moveUpDisabled\"}},[\"↑\"]],[\"button\",{\"title\":\"Move Down\",\"click\":{\"bind\":\"moveDown\"},\"disabled\":{\"bind\":\"moveDownDisabled\"}},[\"↓\"]],[\"button\",{\"title\":\"Duplicate\",\"click\":{\"bind\":\"duplicate\"}},[\"+\"]],[\"button\",{\"title\":\"Remove\",\"click\":{\"bind\":\"remove\"},\"disabled\":{\"bind\":\"removeDisabled\"}},[\"X\"]]]]]]);"
},
"views/arranger": {
"content": "var FXPicker, Jadelet, Modal, RowTemplate, Section, SectionPresenter, Template, dup, _ref;\n\n_ref = system.ui, Jadelet = _ref.Jadelet, Modal = _ref.Modal;\n\nSection = require(\"../lib/section\");\n\nTemplate = require(\"../templates/arranger\");\n\nRowTemplate = require(\"../templates/arranger-row\");\n\nFXPicker = require(\"./fx-picker\");\n\ndup = function(section) {\n return Section(JSON.parse(JSON.stringify(section.toJSON())));\n};\n\nSectionPresenter = function(section, sections, index) {\n var fxPicker;\n fxPicker = FXPicker({\n presetName: section.presetName\n });\n return Object.assign({}, section, {\n duplicate: function() {\n return sections.splice(index + 1, 0, dup(section));\n },\n moveUp: function() {\n sections.remove(section);\n return sections.splice(index - 1, 0, section);\n },\n moveDown: function() {\n sections.remove(section);\n return sections.splice(index + 1, 0, section);\n },\n moveUpDisabled: function() {\n return index === 0;\n },\n moveDownDisabled: function() {\n return index === sections.length - 1;\n },\n remove: function() {\n return sections.remove(section);\n },\n removeDisabled: function() {\n return sections.length <= 1;\n },\n fxPickerElement: fxPicker.element\n });\n};\n\nmodule.exports = function(player) {\n var self;\n if (player.showArranger == null) {\n player.showArranger = function() {\n return Modal.show(self.element);\n };\n }\n self = {\n items: function() {\n return player.sections().map(function(section, i) {\n return RowTemplate(SectionPresenter(section, player.sections, i));\n });\n },\n newSection: function() {\n return player.sections.push(Section());\n },\n element: null\n };\n self.element = Template(self);\n return self;\n};\n"
},
"data/views/arranger": {
"content": "var Observable, Section;\n\nObservable = system.ui.Observable;\n\nSection = require(\"/lib/section\");\n\nmodule.exports = {\n sections: Observable([Section()])\n};\n"
},
"views/actions": {
"content": "var Presenter;\n\nPresenter = function(player) {\n return Object.assign({}, player, {\n length: function() {\n return player.activeSection().length.apply(null, arguments);\n },\n tempo: function() {\n return player.activeSection().tempo.apply(null, arguments);\n }\n });\n};\n\nmodule.exports = function(player) {\n return {\n element: require(\"../templates/actions\")(Presenter(player))\n };\n};\n"
},
"test/main": {
"content": "var Player, Song;\n\nSong = require(\"../song-v2\");\n\nPlayer = require(\"../player\");\n\ndescribe(\"Song V2\", function() {\n it(\"should add notes to the proper sections\", function() {\n var called, song;\n song = Song({\n sections: [\n {\n length: 4\n }, {\n length: 4\n }\n ]\n });\n assert.equal(song.length(), 8);\n song.addNote([0, 0, 0, 0]);\n assert.equal(song.sections()[0].notes().length, 1);\n assert.equal(song.sections()[1].notes().length, 0);\n song.addNote([4, 0, 0, 0]);\n assert.equal(song.sections()[0].notes().length, 1);\n assert.equal(song.sections()[1].notes().length, 1);\n song.addNote([8, 0, 0, 0]);\n assert.equal(song.sections()[0].notes().length, 1);\n assert.equal(song.sections()[1].notes().length, 1);\n called = false;\n song.upcomingNotes(0, 4, function(note) {\n return called = true;\n });\n assert(called);\n called = false;\n song.upcomingNotes(4, 4, function(note) {\n return called = true;\n });\n assert(called);\n called = false;\n song.upcomingNotes(8, 4, function(note) {\n return called = true;\n });\n return assert(!called);\n });\n return it(\"should return the proper time offsets in multi-section multi-tempo settings\", function() {\n var expectations, i, song;\n song = Song({\n sections: [\n {\n length: 1,\n tempo: 120,\n notes: [[0, 0, 0, 0], [0.25, 0, 0, 0], [0.5, 0, 0, 0], [0.75, 0, 0, 0]]\n }, {\n length: 1,\n tempo: 120,\n notes: [[0, 0, 0, 0], [0.25, 0, 0, 0], [0.5, 0, 0, 0], [0.75, 0, 0, 0]]\n }\n ]\n });\n expectations = [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875];\n i = 0;\n song.upcomingNotes(0, 2, function(note, section, s) {\n assert.equal(expectations[i], s);\n return i++;\n });\n assert.equal(i, 8);\n expectations = [0.5, 0.625, 0.75, 0.875];\n i = 0;\n song.upcomingNotes(1, 2, function(note, section, s) {\n assert.equal(expectations[i], s);\n return i++;\n }, 0);\n assert.equal(i, 4);\n expectations = [0, 0.125, 0.25, 0.375];\n i = 0;\n return song.upcomingNotes(1, 2, function(note, section, s) {\n assert.equal(expectations[i], s);\n return i++;\n });\n });\n});\n\ndescribe(\"wrapping\", function() {\n return it(\"should have proper tempo when wrapping\", function() {\n var song;\n song = Song({\n sections: [\n {\n length: 4,\n tempo: 60,\n notes: [[0, 0, 0, 0]]\n }, {\n length: 4,\n tempo: 240\n }\n ]\n });\n return song.upcomingNotes(0, 1, function(note, section, s) {\n return assert.equal(s, 0.25);\n }, -1);\n });\n});\n\ndescribe(\"resection\", function() {\n return it(\"should resection notes\", function() {\n var notes, player;\n player = Player({\n song: {\n sections: [\n {\n length: 4,\n notes: [[0, 0, 0, 0], [1, 0, 0, 0], [2, 0, 0, 0], [3, 0, 0, 0]]\n }, {\n length: 4\n }\n ]\n }\n });\n notes = player.song().sections()[0].notes().slice();\n player.moveNotes(notes, 1, 0);\n assert.deepEqual(player.song().sections()[0].notes(), [[1, 0, 0, 0], [2, 0, 0, 0], [3, 0, 0, 0]]);\n assert.deepEqual(player.song().sections()[1].notes(), [[0, 0, 0, 0]]);\n player.moveNotes(notes, 3, 0);\n assert.deepEqual(player.song().sections()[0].notes(), []);\n assert.deepEqual(player.song().sections()[1].notes(), [[3, 0, 0, 0], [0, 0, 0, 0], [1, 0, 0, 0], [2, 0, 0, 0]]);\n player.moveNotes(notes, -2, 0);\n assert.deepEqual(player.song().sections()[0].notes(), [[2, 0, 0, 0], [3, 0, 0, 0]]);\n return assert.deepEqual(player.song().sections()[1].notes(), [[1, 0, 0, 0], [0, 0, 0, 0]]);\n });\n});\n"
},
"scratch": {
"content": "var computeElapsedBeats, computeElapsedSeconds, playUpcomingSounds;\n\nplayUpcomingSounds = function(current, fromBeat, toBeat) {\n var dt;\n dt = toBeat - fromBeat;\n if (dt <= 0) {\n return;\n }\n return self.upcomingNotes(fromBeat, dt, function(_arg, section, s) {\n var accidental, beat, instrument, pitch, staffNote;\n beat = _arg[0], staffNote = _arg[1], accidental = _arg[2], instrument = _arg[3];\n pitch = staffNoteToPitch(staffNote, accidental);\n return self.playNote(instrument, pitch, s);\n });\n};\n\ncomputeElapsedBeats = function(playTime, elapsedSeconds) {\n var beats, beatsLeft, section, _bps, _ref;\n if (playTime === self.length()) {\n playTime = 0;\n }\n _ref = self.sectionAt(playTime), section = _ref[0], beatsLeft = _ref[1];\n _bps = section.tempo() / 60;\n beats = elapsedSeconds * _bps;\n if (beats > beatsLeft) {\n return beatsLeft + computeElapsedBeats(playTime + beatsLeft, elapsedSeconds - beatsLeft / _bps);\n } else {\n return beats;\n }\n};\n\ncomputeElapsedSeconds = function(t, dt) {\n var beatsLeft, section, _ref;\n return _ref = self.sectionAt(playTime), section = _ref[0], beatsLeft = _ref[1], _ref;\n};\n"
},
"lib/test/song": {
"content": "var Song;\n\nSong = require(\"/song-v2\");\n\ndescribe(\"Song\", function() {\n return it(\"should know its duration in seconds\", function() {\n var song;\n song = Song({\n sections: [\n {\n length: 90,\n tempo: 60\n }, {\n length: 180,\n tempo: 120\n }\n ]\n });\n return assert.equal(song.duration(), 180);\n });\n});\n"
},
"templates/section": {
"content": "module.exports = system.ui.Jadelet.exec([\"song-section\",{\"class\":[{\"bind\":\"meterClass\"}]},[[\"div\",{\"class\":[\"key-signature\",{\"bind\":\"c1\"},{\"bind\":\"c2\"}]},[[\"div\",{},[]],[\"div\",{},[]],[\"div\",{},[]],[\"div\",{},[]],[\"div\",{},[]],[\"div\",{},[]],[\"div\",{},[]]]],[\"span\",{\"class\":[\"measure-number\"]},[\"1\"]],[\"span\",{\"class\":[\"measure-number\"]},[\"5\"]]]]);"
},
"data/templates/arranger-row": {
"content": "module.exports = {\n keySignature: 0,\n timeSignature: \"4/4\"\n};\n"
},
"data/templates/arranger": {
"content": "module.exports = {\n items: Observable([{}, {}]),\n newSection: function() {\n return this.items.push({});\n }\n};\n"
},
"lib/midi-file": {
"content": "\n/*\nRead MIDI data from an array buffer\n\nStatus: Copied from audio-builder actual dev should take place there.\n */\nvar MidiFile, advanceTrack, nextTrackEvent, parseEvent, processEvents, readAscii, readChunk, readEvent, readInt16, readInt24, readInt32, readInt8, readVarInt, subarray;\n\nreadAscii = function(state, length) {\n var data, position;\n data = state.data, position = state.position;\n state.position = position + length;\n return String.fromCharCode.apply(String, data.slice(position, position + length));\n};\n\nsubarray = function(state, length) {\n var data, position;\n data = state.data, position = state.position;\n state.position = position + length;\n return data.subarray(position, position + length);\n};\n\nreadInt32 = function(state) {\n var data, position;\n data = state.data, position = state.position;\n state.position = position + 4;\n return (data[position] << 24) + (data[position + 1] << 16) + (data[position + 2] << 8) + data[position + 3];\n};\n\nreadInt24 = function(state) {\n var data, position;\n data = state.data, position = state.position;\n state.position = position + 3;\n return (data[position] << 16) + (data[position + 1] << 8) + data[position + 2];\n};\n\nreadInt16 = function(state) {\n var data, position;\n data = state.data, position = state.position;\n state.position = position + 2;\n return (data[position] << 8) + data[position + 1];\n};\n\nreadInt8 = function(state, signed) {\n var data, position, result;\n data = state.data, position = state.position;\n state.position = position + 1;\n result = data[position];\n if (signed && result > 127) {\n result -= 256;\n }\n return result;\n};\n\n\n/*\nread a MIDI-style variable-length integer\n(big-endian value in groups of 7 bits,\nwith top bit set to signify that another byte follows)\n */\n\nreadVarInt = function(state) {\n var b, result;\n result = 0;\n while (true) {\n b = readInt8(state);\n if (b & 0x80) {\n result += b & 0x7f;\n result <<= 7;\n } else {\n return result + b;\n }\n }\n};\n\nreadChunk = function(state) {\n var id, length, sub;\n id = readAscii(state, 4);\n length = readInt32(state);\n sub = subarray(state, length);\n return {\n id: id,\n length: length,\n data: sub,\n position: 0\n };\n};\n\nparseEvent = function(_arg) {\n var data, deltaTime, event, eventType, hourByte, lastEventType, length, param1, param2, state, subtype, type;\n deltaTime = _arg.deltaTime, data = _arg.data, lastEventType = _arg.type;\n event = {\n deltaTime: deltaTime\n };\n state = {\n data: data,\n position: 0\n };\n type = readInt8(state);\n if ((type & 0xf0) === 0xf0) {\n if (type === 0xff) {\n event.type = \"meta\";\n subtype = readInt8(state);\n length = readVarInt(state);\n switch (subtype) {\n case 0x00:\n event.subtype = 'sequenceNumber';\n if (length !== 2) {\n throw 'Expected length for sequenceNumber event is 2, got ' + length;\n }\n event.number = readInt16(state);\n break;\n case 0x01:\n event.subtype = 'text';\n event.text = readAscii(state, length);\n break;\n case 0x02:\n event.subtype = 'copyrightNotice';\n event.text = readAscii(state, length);\n break;\n case 0x03:\n event.subtype = 'trackName';\n event.text = readAscii(state, length);\n break;\n case 0x04:\n event.subtype = 'instrumentName';\n event.text = readAscii(state, length);\n break;\n case 0x05:\n event.subtype = 'lyrics';\n event.text = readAscii(state, length);\n break;\n case 0x06:\n event.subtype = 'marker';\n event.text = readAscii(state, length);\n break;\n case 0x07:\n event.subtype = 'cuePoint';\n event.text = readAscii(state, length);\n break;\n case 0x20:\n event.subtype = 'midiChannelPrefix';\n if (length !== 1) {\n throw 'Expected length for midiChannelPrefix event is 1, got ' + length;\n }\n event.channel = readInt8(state);\n break;\n case 0x2f:\n event.subtype = 'endOfTrack';\n if (length) {\n throw 'Expected length for endOfTrack event is 0, got ' + length;\n }\n break;\n case 0x51:\n event.subtype = 'setTempo';\n if (length !== 3) {\n throw 'Expected length for setTempo event is 3, got ' + length;\n }\n event.microsecondsPerBeat = readInt24(state);\n break;\n case 0x54:\n event.subtype = 'smpteOffset';\n if (length !== 5) {\n throw 'Expected length for smpteOffset event is 5, got ' + length;\n }\n hourByte = readInt8(state);\n event.frameRate = {\n 0x00: 24,\n 0x20: 25,\n 0x40: 29,\n 0x60: 30\n }[hourByte & 0x60];\n event.hour = hourByte & 0x1f;\n event.min = readInt8(state);\n event.sec = readInt8(state);\n event.frame = readInt8(state);\n event.subframe = readInt8(state);\n break;\n case 0x58:\n event.subtype = 'timeSignature';\n if (length !== 4) {\n throw 'Expected length for timeSignature event is 4, got ' + length;\n }\n event.numerator = readInt8(state);\n event.denominator = Math.pow(2, readInt8(state));\n event.metronome = readInt8(state);\n event.thirtyseconds = readInt8(state);\n break;\n case 0x59:\n event.subtype = 'keySignature';\n if (length !== 2) {\n throw 'Expected length for keySignature event is 2, got ' + length;\n }\n event.key = readInt8(state, true);\n event.scale = readInt8(state);\n break;\n case 0x7f:\n event.subtype = 'sequencerSpecific';\n event.data = subarray(state, length);\n break;\n default:\n event.subtype = 'unknown';\n event.data = subarray(state, length);\n }\n } else if (type === 0xf0) {\n event.type = 'sysEx';\n length = readVarInt(state);\n event.data = subarray(state, length);\n } else if (type === 0xf7) {\n event.type = 'dividedSysEx';\n length = readVarInt(state);\n event.data = subarray(state, length);\n } else {\n throw 'Unrecognised MIDI event type byte: ' + type;\n }\n } else {\n if ((type & 0x80) === 0) {\n param1 = type;\n type = lastEventType;\n } else {\n param1 = readInt8(state);\n }\n eventType = type >> 4;\n event.channel = type & 0x0f;\n event.type = 'channel';\n switch (eventType) {\n case 0x08:\n event.subtype = 'noteOff';\n event.noteNumber = param1;\n event.velocity = readInt8(state);\n break;\n case 0x09:\n if (event.velocity === 0) {\n event.subtype = 'noteOff';\n } else {\n event.subtype = 'noteOn';\n }\n event.noteNumber = param1;\n event.velocity = readInt8(state);\n break;\n case 0x0a:\n event.subtype = 'noteAftertouch';\n event.noteNumber = param1;\n event.amount = readInt8(state);\n break;\n case 0x0b:\n event.subtype = 'controller';\n event.controllerType = param1;\n event.value = readInt8(state);\n break;\n case 0x0c:\n event.subtype = 'programChange';\n event.programNumber = param1;\n break;\n case 0x0d:\n event.subtype = 'channelAftertouch';\n event.amount = param1;\n break;\n case 0x0e:\n event.subtype = 'pitchBend';\n param2 = readInt8(state);\n event.value = param1 + (param2 << 7);\n break;\n default:\n throw 'Unrecognised MIDI event type: ' + eventType;\n }\n }\n return event;\n};\n\nreadEvent = function(state) {\n var data, deltaTime, length, metaType, offset, param1, position, raw, start, type;\n position = state.position, data = state.data;\n deltaTime = readVarInt(state);\n start = state.position;\n type = readInt8(state);\n switch (type) {\n case 0xff:\n metaType = readInt8(state);\n length = readVarInt(state);\n offset = state.position - start;\n state.position = start;\n raw = subarray(state, length + offset);\n break;\n case 0xf0:\n case 0xf7:\n length = readVarInt(state);\n offset = state.position - start;\n state.position = start;\n raw = subarray(state, length + offset);\n break;\n default:\n if ((type & 0x80) === 0) {\n param1 = type;\n type = state.lastEventType;\n } else {\n param1 = readInt8(state);\n state.lastEventType = type;\n }\n offset = state.position - start;\n state.position = start;\n switch (type >> 4) {\n case 0xc:\n case 0xd:\n raw = subarray(state, offset);\n break;\n default:\n raw = subarray(state, offset + 1);\n }\n }\n return {\n deltaTime: deltaTime,\n type: type,\n data: raw\n };\n};\n\nprocessEvents = function(track) {\n var data, i, lastEventType, length, position, _results;\n position = 0;\n lastEventType = 0;\n data = track.data;\n length = data.length;\n i = 0;\n _results = [];\n while (track.position < length && i < 1000) {\n i++;\n _results.push(readEvent(track));\n }\n return _results;\n};\n\nadvanceTrack = function(state) {\n state.current = readEvent(state);\n return state.ticks = 0;\n};\n\nnextTrackEvent = function(state) {\n var event, formatType, minTicks, nextTrack, trackIndex, tracks;\n formatType = state.formatType, tracks = state.tracks;\n if (formatType === 0 || formatType === 1) {\n minTicks = Infinity;\n nextTrack = void 0;\n trackIndex = 0;\n tracks.forEach(function(track, i) {\n var at;\n at = track.current.deltaTime - track.ticks;\n if (at < minTicks) {\n trackIndex = i;\n minTicks = at;\n return nextTrack = track;\n }\n });\n if (nextTrack) {\n tracks.forEach(function(track) {\n return track.ticks += minTicks;\n });\n event = nextTrack.current;\n advanceTrack(nextTrack);\n event.deltaTime = minTicks;\n event.track = trackIndex;\n return event;\n }\n } else {\n throw \"MIDI format type \" + formatType + \" not supported yet\";\n }\n};\n\nmodule.exports = MidiFile = function(data) {\n var header, lastEventType, self, timeDivision, trackCount, _i, _results;\n lastEventType = 0;\n self = {\n data: data,\n formatType: -1,\n position: 0,\n ticksPerBeat: -1,\n tracks: void 0\n };\n header = readChunk(self);\n if (header.id !== 'MThd' || header.length !== 6) {\n throw 'MIDI header not found';\n }\n self.formatType = readInt16(header);\n trackCount = readInt16(header);\n timeDivision = readInt16(header);\n if (timeDivision & 0x8000) {\n throw 'Expressing time division in SMTPE frames is not supported yet';\n } else {\n self.ticksPerBeat = timeDivision;\n }\n self.tracks = (function() {\n _results = [];\n for (var _i = 0; 0 <= trackCount ? _i < trackCount : _i > trackCount; 0 <= trackCount ? _i++ : _i--){ _results.push(_i); }\n return _results;\n }).apply(this).map(function(i) {\n var track, trackChunk;\n trackChunk = readChunk(self);\n if (trackChunk.id !== 'MTrk') {\n throw 'Unexpected chunk - expected MTrk, got ' + trackChunk.id;\n }\n track = {\n data: trackChunk.data,\n position: 0,\n ticks: 0,\n lastEventType: 0\n };\n advanceTrack(track);\n return track;\n });\n return self;\n};\n\nObject.assign(MidiFile, {\n nextTrackEvent: nextTrackEvent,\n parseEvent: parseEvent,\n readEvent: readEvent,\n readInt8: readInt8,\n readInt16: readInt16,\n readInt24: readInt24,\n readInt32: readInt32,\n readVarInt: readVarInt,\n readAscii: readAscii\n});\n"
},
"lib/midi2composer": {
"content": "var MIDIFile, drums, inverseScale, microsecondsPerMinute, missing, mod, nextTrackEvent, parseEvent, pitchToStaffNote, programs, scale, _ref;\n\n_ref = MIDIFile = require(\"./midi-file\"), nextTrackEvent = _ref.nextTrackEvent, parseEvent = _ref.parseEvent;\n\nmicrosecondsPerMinute = 60000000;\n\npitchToStaffNote = function(n) {\n var a, o, r, staffNote;\n r = mod(n, 12);\n o = Math.floor(n / 12);\n staffNote = inverseScale[r];\n if (staffNote != null) {\n a = 0;\n } else {\n staffNote = inverseScale[r - 1];\n a = 1;\n }\n return [o * 7 + staffNote, a];\n};\n\nmod = function(n, k) {\n return (n % k + k) % k;\n};\n\nscale = {\n \"-7\": {\n 0: -1,\n 1: 1,\n 2: 3,\n 3: 4,\n 4: 6,\n 5: 8,\n 6: 10\n },\n \"-6\": {\n 0: -1,\n 1: 1,\n 2: 3,\n 3: 5,\n 4: 6,\n 5: 8,\n 6: 10\n },\n \"-5\": {\n 0: 0,\n 1: 1,\n 2: 3,\n 3: 5,\n 4: 6,\n 5: 8,\n 6: 10\n },\n \"-4\": {\n 0: 0,\n 1: 1,\n 2: 3,\n 3: 5,\n 4: 7,\n 5: 8,\n 6: 10\n },\n \"-3\": {\n 0: 0,\n 1: 2,\n 2: 3,\n 3: 5,\n 4: 7,\n 5: 8,\n 6: 10\n },\n \"-2\": {\n 0: 0,\n 1: 2,\n 2: 3,\n 3: 5,\n 4: 7,\n 5: 9,\n 6: 10\n },\n \"-1\": {\n 0: 0,\n 1: 2,\n 2: 4,\n 3: 5,\n 4: 7,\n 5: 9,\n 6: 10\n },\n 0: {\n 0: 0,\n 1: 2,\n 2: 4,\n 3: 5,\n 4: 7,\n 5: 9,\n 6: 11\n },\n 1: {\n 0: 0,\n 1: 2,\n 2: 4,\n 3: 6,\n 4: 7,\n 5: 9,\n 6: 11\n },\n 2: {\n 0: 1,\n 1: 2,\n 2: 4,\n 3: 6,\n 4: 7,\n 5: 9,\n 6: 11\n },\n 3: {\n 0: 1,\n 1: 2,\n 2: 4,\n 3: 6,\n 4: 8,\n 5: 9,\n 6: 11\n },\n 4: {\n 0: 1,\n 1: 3,\n 2: 4,\n 3: 6,\n 4: 8,\n 5: 9,\n 6: 11\n },\n 5: {\n 0: 1,\n 1: 3,\n 2: 4,\n 3: 6,\n 4: 8,\n 5: 10,\n 6: 11\n },\n 6: {\n 0: 1,\n 1: 3,\n 2: 5,\n 3: 6,\n 4: 8,\n 5: 10,\n 6: 11\n },\n 7: {\n 0: 1,\n 1: 3,\n 2: 5,\n 3: 6,\n 4: 8,\n 5: 10,\n 6: 12\n }\n};\n\ninverseScale = Object.keys(scale[0]).reduce(function(m, key) {\n var value;\n value = scale[0][key];\n m[value] = key | 0;\n return m;\n}, {});\n\nprograms = {\n 0: 1,\n 1: 1,\n 2: 1,\n 3: 1,\n 4: 1,\n 5: 1,\n 6: 1,\n 7: 1,\n 8: 6,\n 9: 6,\n 10: 6,\n 11: 6,\n 12: 6,\n 13: 6,\n 14: 6,\n 15: 6,\n 16: 7,\n 17: 7,\n 18: 7,\n 19: 7,\n 20: 7,\n 21: 7,\n 22: 7,\n 23: 7,\n 24: 19,\n 25: 2,\n 26: 2,\n 27: 2,\n 28: 2,\n 29: 2,\n 30: 19,\n 31: 6,\n 32: 3,\n 33: 3,\n 34: 3,\n 35: 3,\n 36: 3,\n 37: 3,\n 38: 3,\n 39: 3,\n 40: 0,\n 41: 16,\n 42: 0,\n 43: 17,\n 44: 4,\n 45: 19,\n 46: 6,\n 47: 10,\n 48: 18,\n 49: 18,\n 50: 18,\n 51: 18,\n 52: 17,\n 53: 16,\n 54: 16,\n 55: 5,\n 56: 4,\n 57: 4,\n 58: 4,\n 59: 4,\n 60: 4,\n 61: 4,\n 62: 4,\n 63: 4,\n 64: 17,\n 65: 15,\n 66: 4,\n 67: 4,\n 68: 4,\n 69: 4,\n 70: 4,\n 71: 4,\n 72: 2,\n 73: 6,\n 74: 1,\n 75: 0,\n 76: 0,\n 77: 18,\n 78: 18,\n 79: 6,\n 80: 0,\n 81: 0,\n 82: 0,\n 83: 0,\n 84: 0,\n 85: 16,\n 86: 0,\n 87: 0,\n 88: 13,\n 89: 18,\n 90: 18,\n 91: 7,\n 92: 7,\n 93: 18,\n 94: 7,\n 95: 18,\n 96: 13,\n 97: 14,\n 98: 6,\n 99: 13,\n 100: 6,\n 101: 14,\n 102: 17,\n 103: 13,\n 104: 17,\n 105: 19,\n 106: 19,\n 107: 19,\n 108: 19,\n 109: 17,\n 110: 1,\n 111: 4,\n 112: 6,\n 113: 6,\n 114: 1,\n 115: 10,\n 116: 8,\n 117: 8,\n 118: 8,\n 119: 12,\n 120: 17,\n 121: 13,\n 122: 13,\n 123: 4,\n 124: 0,\n 125: 9,\n 126: 9,\n 127: 9\n};\n\ndrums = {\n 12: [],\n 20: [[10, 10]],\n 27: [[20, 7]],\n 28: [[20, 7]],\n 29: [],\n 30: [],\n 31: [[10, 7]],\n 32: [[10, 10]],\n 33: [[20, 0], [10, 4]],\n 34: [[8, -4]],\n 35: [[8, -5]],\n 36: [[10, 8]],\n 37: [[9, 0]],\n 38: [[11, 0]],\n 39: [[20, 0]],\n 40: [[8, 1]],\n 41: [[12, 11]],\n 42: [[8, 3]],\n 43: [[12, -1]],\n 44: [[8, 4]],\n 45: [[12, -3]],\n 46: [[8, 6]],\n 47: [[8, 7]],\n 48: [[12, -11]],\n 49: [[8, 8]],\n 50: [[12, -9]],\n 51: [[12, -12], [12, -10]],\n 52: [[6, 7]],\n 53: [[15, 16], [12, 16]],\n 54: [[12, -7]],\n 55: [[10, -4]],\n 56: [[12, -14]],\n 57: [[15, -1]],\n 58: [],\n 59: [[8, 8]],\n 60: [[8, 6]],\n 61: [[8, -2], [8, 6]],\n 62: [[8, -2]],\n 63: [[8, -6]],\n 64: [[10, 5]],\n 65: [[10, -5]],\n 66: [[6, 4]],\n 67: [[6, -2]],\n 68: [[11, 10]],\n 69: [[11, 7]],\n 70: [[18, 14]],\n 71: [],\n 72: [],\n 73: [],\n 74: [],\n 75: [[10, 0]],\n 76: [[10, -5]],\n 77: [],\n 78: [],\n 79: [],\n 80: [[12, 2]],\n 81: [],\n 82: [],\n 83: [[6, -14], [6, -7], [6, 0], [6, 7], [6, 14]],\n 84: [],\n 85: [],\n 86: [],\n 87: [[11, -7], [11, 0], [11, 7]],\n 88: [],\n 89: [],\n 90: [],\n 91: [],\n 93: [],\n 94: [],\n 95: [],\n 96: [],\n 121: []\n};\n\nmissing = {};\n\nmodule.exports = function(buffer) {\n var acc, channels, data, event, events, maxT, midiFile, n, section, sections, ticksPerBeat;\n midiFile = MIDIFile(buffer);\n events = [];\n while (event = nextTrackEvent(midiFile)) {\n events.push(parseEvent(event));\n }\n ({\n midi: midiFile\n });\n ticksPerBeat = midiFile.ticksPerBeat;\n acc = 0;\n section = {\n name: \"Section 1\",\n tempo: 120,\n notes: []\n };\n sections = [section];\n channels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].map(function() {\n return {\n instrument: 0\n };\n });\n data = events.map(function(e) {\n var deltaTime;\n deltaTime = e.deltaTime;\n acc += deltaTime;\n e.t = acc;\n return e;\n });\n maxT = acc / ticksPerBeat;\n n = 1;\n acc = 0;\n data.forEach(function(_arg) {\n var accidental, beat, channel, instrument, mappings, microsecondsPerBeat, noteNumber, programNumber, staffNote, subtype, t, velocity, _ref1;\n t = _arg.t, noteNumber = _arg.noteNumber, subtype = _arg.subtype, microsecondsPerBeat = _arg.microsecondsPerBeat, channel = _arg.channel, programNumber = _arg.programNumber, velocity = _arg.velocity;\n beat = t / ticksPerBeat - acc;\n switch (subtype) {\n case \"setTempo\":\n if (section.notes.length) {\n acc += beat;\n section.length = beat;\n section = {\n name: \"Section \" + (++n),\n tempo: Math.round(microsecondsPerMinute / microsecondsPerBeat),\n notes: []\n };\n return sections.push(section);\n } else {\n return section.tempo = Math.round(microsecondsPerMinute / microsecondsPerBeat);\n }\n break;\n case \"programChange\":\n instrument = programs[programNumber];\n if (instrument != null) {\n return channels[channel] = programs[programNumber];\n } else {\n return missing[programNumber] = true;\n }\n break;\n case \"noteOn\":\n if (velocity === 0) {\n return;\n }\n _ref1 = pitchToStaffNote(noteNumber - 60), staffNote = _ref1[0], accidental = _ref1[1];\n if (channel === 9) {\n mappings = drums[noteNumber];\n if (!mappings) {\n mappings = drums[noteNumber] = [];\n if (missing.drums == null) {\n missing.drums = {};\n }\n missing.drums[noteNumber] = true;\n }\n return mappings.forEach(function(_arg1) {\n var instrument, staffNote;\n instrument = _arg1[0], staffNote = _arg1[1];\n return section.notes.push([beat, staffNote, 0, instrument]);\n });\n } else {\n instrument = channels[channel];\n if (instrument === 3) {\n staffNote += 7;\n }\n return section.notes.push([beat, staffNote, accidental, instrument]);\n }\n }\n });\n section.length = maxT + 4;\n console.warn(\"MISSING:\", missing);\n return {\n sections: sections,\n version: 2\n };\n};\n"
},
"views/sprites": {
"content": "var Jadelet, Modal, Observable, RowTemplate, Template, _ref;\n\n_ref = system.ui, Jadelet = _ref.Jadelet, Modal = _ref.Modal, Observable = _ref.Observable;\n\nTemplate = Jadelet.exec(\"section.settings\\n h2 ⚙️ Settings\\n button.close(click=@close) X\\n table\\n thead\\n tr\\n th Name\\n th Volume\\n th Pan\\n tbody\\n @items\");\n\nRowTemplate = Jadelet.exec(\"tr\\n td.sprite\\n img(src=@url)\\n @name\\n td.input\\n input(type=\\\"range\\\" min=0 max=2 step=0.01 value=@volume)\\n input(type=\\\"number\\\" min=0 max=2 step=0.01 value=@volume)\\n td.input\\n input(type=\\\"range\\\" min=-1 max=1 step=0.01 value=@pan)\\n input(type=\\\"number\\\" min=-1 max=1 step=0.01 value=@pan)\\n td.input\\n input(type=\\\"range\\\" min=-12 max=12 step=1 value=@pitchShift)\\n input(type=\\\"number\\\" min=-12 max=12 step=1 value=@pitchShift)\");\n\nmodule.exports = function(player) {\n var self;\n if (player.showSpriteConfig == null) {\n player.showSpriteConfig = function() {\n return Modal.show(self.element);\n };\n }\n self = {\n close: function() {\n return Modal.hide();\n },\n items: function() {\n return player.samples.map(function(sample) {\n var name, pan, pitchShift, url, volume;\n name = sample.name, url = sample.url;\n volume = Observable(sample.volume);\n volume.observe(function(v) {\n return sample.volume = Number(v);\n });\n pan = Observable(sample.pan);\n pan.observe(function(v) {\n return sample.pan = Number(v);\n });\n pitchShift = Observable(sample.pitchShift);\n pitchShift.observe(function(v) {\n return sample.pitchShift = Number(v);\n });\n return RowTemplate({\n url: url,\n name: name,\n volume: volume,\n pan: pan,\n pitchShift: pitchShift\n });\n });\n },\n element: null\n };\n self.element = Template(self);\n return self;\n};\n"
},
"views/settings": {
"content": "var Jadelet, Modal, Observable, RowTemplate, Template, _ref;\n\n_ref = system.ui, Jadelet = _ref.Jadelet, Modal = _ref.Modal, Observable = _ref.Observable;\n\nTemplate = Jadelet.exec(\"section.settings\\n h2 ⚙️ Settings\\n button.close(click=@close) X\\n table\\n thead\\n tr\\n th Name\\n th Volume\\n th Pan\\n th Pitch Shift\\n tbody\\n @items\");\n\nRowTemplate = Jadelet.exec(\"tr\\n td.sprite\\n img(src=@url)\\n @name\\n td.input\\n input(type=\\\"range\\\" min=0 max=2 step=0.01 value=@volume)\\n input(type=\\\"number\\\" min=0 max=2 step=0.01 value=@volume)\\n td.input\\n input(type=\\\"range\\\" min=-1 max=1 step=0.01 value=@pan)\\n input(type=\\\"number\\\" min=-1 max=1 step=0.01 value=@pan)\\n td.input\\n input(type=\\\"range\\\" min=-12 max=12 step=1 value=@pitchShift)\\n input(type=\\\"number\\\" min=-12 max=12 step=1 value=@pitchShift)\");\n\nmodule.exports = function(player) {\n var self;\n if (player.showSpriteConfig == null) {\n player.showSpriteConfig = function() {\n return Modal.show(self.element);\n };\n }\n self = {\n close: function() {\n return Modal.hide();\n },\n items: function() {\n return player.song().settings().map(RowTemplate);\n },\n element: null\n };\n self.element = Template(self);\n return self;\n};\n"
}
},
"progenitor": {
"url": "https://danielx.net/editor/?code=c2d054f7ed34c13e0229"
},
"config": {
"title": "Mario Paint Music Composer - danielx.net",
"description": "This Mario Paint inspired composer tool is easy and fun. You can create simple\nand beautiful songs right in your browser and share them with the world!",
"iconURL": "assets/images/raccoon.png",
"version": "0.4.7",
"publishPath": "/My Briefcase/public/danielx.net/",
"name": "composer",
"dependencies": {
"!system": "https://danielx.net/system/0.5.0-pre.4.json"
},
"remoteDependencies": [
"https://js.stripe.com/v3/"
],
"width": 1024,
"height": 960,
"sandbox": "allow-same-origin allow-modals allow-forms allow-pointer-lock allow-popups allow-scripts allow-popups-to-escape-sandbox midi",
"cognito": {
"identityPoolId": "us-east-1:4fe22da5-bb5e-4a78-a260-74ae0a140bf9",
"poolData": {
"UserPoolId": "us-east-1_cfvrlBLXG",
"ClientId": "3fd84r6idec9iork4e9l43mp61"
},
"bucket": "whimsy-fs"
},
"manifest": {
"name": "DanielX.net Paint Composer",
"short_name": "Composer",
"display": "standalone",
"start_url": ".",
"scope": "./",
"icons": [
{
"src": "assets/images/raccoon.png",
"sizes": "48x48"
},
{
"src": "assets/icon256.png",
"sizes": "256x256"
}
]
}
},
"version": "0.1.0",
"repository": {
"branch": "master",
"default_branch": "master",
"full_name": "STRd6/composer",
"homepage": null,
"description": "Compose music on the internets?",
"html_url": "https://github.com/STRd6/composer",
"url": "https://api.github.com/repos/STRd6/composer",
"publishBranch": "gh-pages"
},
"dependencies": {
"!system": {
"distribution": {
"NOTES": {},
"README": {},
"TODO": {},
"action": {
"content": "\n/*\nAction\n======\n\nActions have a function to call, a hotkey, and a function that determines\nwhether or not they are disabled. This is so we can present them in the UI for\nmenus.\n\nThe hotkey is for display purposes only and needs to be listened to by a\nseparate mechanism to perform. [TODO] The action can be executed like a regular\nfunction (instead of needing to use call).\n\nActions may have icons and help text as well.\n */\nvar Observable,\n __slice = [].slice;\n\nObservable = require(\"./lib/observable\");\n\nmodule.exports = function(fn, hotkey) {\n var disabled;\n disabled = Observable(false);\n setInterval(function() {\n return disabled.toggle();\n }, 1000);\n return {\n disabled: disabled,\n hotkey: function() {\n return hotkey;\n },\n call: function() {\n var args;\n args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n return fn.call.apply(fn, args);\n }\n };\n};\n"
},
"data/templates/input": {
"content": "\n"
},
"data/templates/menu": {
"content": "module.exports = {\n items: [\"yolo\"]\n};\n"
},
"data/templates/modal": {
"content": "\n"
},
"data/templates/modal/prompt": {
"content": "\n"
},
"data/templates/progress": {
"content": "module.exports = {\n value: 0.25,\n min: 0,\n max: 1\n};\n"
},
"data/templates/samples/test-form": {
"content": "\n"
},
"data/templates/table": {
"content": "\n"
},
"data/templates/window": {
"content": "\n"
},
"data/views/context-menu": {
"content": "var Util, parseMenu;\n\nUtil = require(\"../../lib/ui\").Util;\n\nparseMenu = Util.parseMenu;\n\nmodule.exports = {\n items: parseMenu(\"Hello\"),\n handlers: {}\n};\n"
},
"data/views/menu-bar": {
"content": "module.exports = {\n items: \"Hello\\n Yo\",\n handlers: {\n yo: function() {\n return alert(\"yo\");\n }\n }\n};\n"
},
"data/views/menu-item": {
"content": "module.exports = {\n label: \"yo23\",\n contextRoot: {\n handlers: {},\n activeItem: function() {}\n }\n};\n"
},
"data/views/menu-separator": {
"content": "module.exports = {};\n"
},
"data/views/menu": {
"content": "var Util, parseMenu;\n\nUtil = require(\"../../lib/ui\").Util;\n\nparseMenu = Util.parseMenu;\n\nmodule.exports = {\n items: parseMenu(\"Hello\\n\"),\n handlers: {}\n};\n"
},
"data/views/progress": {
"content": "var Observable, model;\n\nObservable = require(\"../../lib/observable\");\n\nmodule.exports = model = {\n max: 1,\n value: Observable(0.25)\n};\n\nsetInterval(function() {\n return model.value.increment(0.01);\n}, 100);\n"
},
"data/views/size": {
"content": "\n"
},
"data/views/table": {
"content": "module.exports = {};\n"
},
"data/views/window": {
"content": "\n"
},
"demo": {
"content": "var ContextMenu, FormSampleTemplate, MenuBar, Modal, Observable, Progress, Style, Table, Window, addWindow, applyStyle, contextMenu, desktop, element, notepadMenuParsed, notepadMenuText, o, parseMenu, sampleMenuParsed, _ref, _ref1, _ref2;\n\n_ref = require(\"./lib/ui\"), ContextMenu = _ref.ContextMenu, MenuBar = _ref.MenuBar, Modal = _ref.Modal, Observable = _ref.Observable, (_ref1 = _ref.Util, parseMenu = _ref1.parseMenu), Progress = _ref.Progress, Style = _ref.Style, Table = _ref.Table, Window = _ref.Window;\n\n_ref2 = require(\"./util\"), applyStyle = _ref2.applyStyle, o = _ref2.o;\n\nnotepadMenuText = require(\"./samples/notepad-menu\");\n\nnotepadMenuParsed = parseMenu(notepadMenuText);\n\nFormSampleTemplate = require(\"./samples/test-form\");\n\napplyStyle(Style.all, \"system\");\n\nsampleMenuParsed = parseMenu(\"[M]odal\\n [A]lert\\n [C]onfirm\\n [P]rompt\\n [F]orm\\n P[r]ogress\\n[T]est Nesting\\n Test[1]\\n Hello\\n Wat\\n Test[2]\\n [N]ested\\n -----\\n [R]ad\\n So Rad\\n =====\\n Hella\\n Hecka\\n Super Hecka\\n Wicked\\n ---\\n -\\n -\\n ==\\n[W]indow\\n New [I]mage -> newImage\\n New [P]ixel -> newPixel\\n New [T]ext -> newText\\n New [S]preadsheet -> newSheet\");\n\nelement = MenuBar({\n items: sampleMenuParsed,\n handlers: {\n alert: function() {\n return Modal.alert(\"yolo\");\n },\n prompt: function() {\n return Modal.prompt(\"Pretty cool, eh?\", \"Yeah!\").then(console.log);\n },\n confirm: function() {\n return Modal.confirm(\"Jawsome!\").then(console.log);\n },\n form: function() {\n return Modal.form(FormSampleTemplate()).then(console.log);\n },\n progress: function() {\n var initialMessage, intervalId, progressView;\n initialMessage = \"Reticulating splines\";\n progressView = Progress({\n value: 0,\n max: 2,\n message: initialMessage\n });\n Modal.show(progressView.element, {\n cancellable: false\n });\n return intervalId = setInterval(function() {\n var ellipses, ellipsesCount, newValue, _i, _results;\n newValue = progressView.value() + 1 / 60;\n ellipsesCount = Math.floor(newValue * 4) % 4;\n ellipses = (function() {\n _results = [];\n for (var _i = 0; 0 <= ellipsesCount ? _i < ellipsesCount : _i > ellipsesCount; 0 <= ellipsesCount ? _i++ : _i--){ _results.push(_i); }\n return _results;\n }).apply(this).map(function() {\n return \".\";\n }).join(\"\");\n progressView.value(newValue);\n progressView.message(initialMessage + ellipses);\n if (newValue > 2) {\n clearInterval(intervalId);\n return Modal.hide();\n }\n }, 15);\n },\n newImage: function() {\n var img;\n img = document.createElement(\"img\");\n img.src = \"https://s3.amazonaws.com/whimsyspace-databucket-1g3p6d9lcl6x1/danielx/data/pI1mvEvxcXJk4mNHNUW-kZsNJsrPDXcAtgguyYETRXQ\";\n return addWindow({\n title: \"Yoo\",\n content: img,\n iconEmoji: \"💼\"\n });\n },\n newPixel: function() {\n var frame;\n frame = document.createElement(\"iframe\");\n frame.src = \"https://danielx.net/pixel-editor/embedded/\";\n return addWindow({\n title: \"Pixel\",\n content: frame\n });\n },\n newText: function() {\n var textarea;\n textarea = document.createElement(\"textarea\");\n return addWindow({\n title: \"Notepad.exe\",\n content: textarea\n });\n },\n newSheet: function() {\n var InputTemplate, RowElement, data, menuBar, tableView;\n data = [0, 1, 2, 3, 4].map(function(i) {\n return {\n id: i,\n name: \"yolo\",\n color: \"#FF0000\"\n };\n });\n InputTemplate = require(\"./templates/input\");\n RowElement = function(datum) {\n var tr, types;\n tr = document.createElement(\"tr\");\n types = [\"number\", \"text\", \"color\"];\n Object.keys(datum).forEach(function(key, i) {\n var td;\n td = document.createElement(\"td\");\n td.appendChild(InputTemplate({\n value: o(datum, key),\n type: types[i]\n }));\n return tr.appendChild(td);\n });\n return tr;\n };\n element = (tableView = Table({\n data: data,\n RowElement: RowElement\n })).element;\n menuBar = MenuBar({\n items: parseMenu(\"Insert\\n Row -> insertRow\\nHelp\\n About\"),\n handlers: {\n about: function() {\n return Modal.alert(\"Spreadsheet v0.0.1 by Daniel X Moore\");\n },\n insertRow: function() {\n data.push({\n id: 50,\n name: \"new\",\n color: \"#FF00FF\"\n });\n return tableView.render();\n }\n }\n });\n return addWindow({\n title: \"Spreadsheet [DEMO VERSION]\",\n content: element,\n menuBar: menuBar.element\n });\n }\n }\n}).element;\n\ndocument.body.appendChild(element);\n\ndesktop = document.createElement(\"desktop\");\n\ndocument.body.appendChild(desktop);\n\ncontextMenu = ContextMenu({\n items: sampleMenuParsed[1][1],\n handlers: {}\n});\n\ndesktop.addEventListener(\"contextmenu\", function(e) {\n if (e.target === desktop) {\n e.preventDefault();\n return contextMenu.display({\n inElement: document.body,\n x: e.pageX,\n y: e.pageY\n });\n }\n});\n\naddWindow = function(params) {\n var windowView;\n windowView = Window(params);\n desktop.appendChild(windowView.element);\n return windowView;\n};\n"
},
"docs/Bindable": {},
"docs/app": {},
"docs/getting-started": {},
"hotkeys": {
"content": "\n/*\nHotkeys\n=======\n\nBind hotkeys\n\n Hotkey = require \"hotkey\"\n\n Hotkey \"ctrl+r\", ->\n alert \"radical!\"\n\nWe'd like to be able to generate a list of hotkeys with descriptions.\n\nQuestions\n---------\n\nShould we just use Mousetrap?\n\nMaybe, but it may have different semantics with preventDefault/defaultPrevented.\n\nShould we allow binding to specific elements?\n\nImagine a windowing OS where non-iframe apps are inside draggable windows. We'd\nlike to have each 'app' able to have its own hotkeys and at the same time have\nglobal OS level hotkeys.\n\nShould `defaultPrevented` prevent executing the hotkey action? Yes\n\nShould executing a hotkey preventDefault? Yes\n */\nmodule.exports = function(element) {\n var handle, handlers;\n handlers = {};\n return handle = function(event) {\n var combo, key, modifiersActive;\n key = event.key;\n modifiersActive = [\"alt\", \"ctrl\", \"meta\", \"shift\"].filter(function(modifier) {\n return event[\"\" + modifier + \"Key\"];\n });\n combo = modifiersActive.concat(key).join(\"+\");\n return typeof handlers[combo] === \"function\" ? handlers[combo](e) : void 0;\n };\n};\n"
},
"lib/app/index": {
"content": "var Bindable, Drop, Jadelet, MenuBar, Modal, Mousetrap, Observable, TemplateLoader, UI, applyStyle, crudeRequire, getProp, _ref, _ref1;\n\n_ref = UI = require(\"../ui\"), Drop = _ref.Drop, Jadelet = _ref.Jadelet, MenuBar = _ref.MenuBar, Modal = _ref.Modal, Observable = _ref.Observable;\n\nBindable = require(\"../bindable\");\n\nMousetrap = require(\"../mousetrap\");\n\n_ref1 = require(\"../../util\"), applyStyle = _ref1.applyStyle, crudeRequire = _ref1.crudeRequire;\n\nTemplateLoader = require(\"./template-loader\");\n\nmodule.exports = function(host, application) {\n return function(app) {\n var ReaderInput, _ref2;\n if (app == null) {\n app = {};\n }\n if (app.saved == null) {\n app.saved = Observable(true);\n }\n if (app.currentPath == null) {\n app.currentPath = Observable(\"\");\n }\n Bindable(null, app);\n Object.assign(app, {\n confirmUnsaved: function() {\n if (app.saved()) {\n return Promise.resolve();\n }\n return new Promise(function(resolve, reject) {\n return Modal.confirm(\"You will lose unsaved progress, continue?\").then(function(result) {\n if (result) {\n return resolve();\n } else {\n return reject();\n }\n });\n });\n },\n hotkey: function(key, method) {\n return Mousetrap.bind(key, function(e) {\n e.preventDefault();\n if (typeof method === \"function\") {\n return method(e, app);\n } else {\n return app[method](e);\n }\n });\n },\n exit: function() {\n return application.exit();\n },\n extend: Object.assign.bind(null, app),\n drop: function(files) {\n app.loadFile(files[0]);\n return true;\n },\n paste: function(files) {\n app.loadFile(files[0]);\n return true;\n },\n \"new\": function() {\n if (app.saved()) {\n app.currentPath(\"\");\n return app.newFile();\n } else {\n return app.confirmUnsaved().then(function() {\n app.saved(true);\n return app.newFile();\n });\n }\n },\n open: function() {\n return app.confirmUnsaved().then(function() {\n return Modal.prompt(\"File Path\", app.currentPath()).then(function(newPath) {\n if (newPath) {\n return app.currentPath(newPath);\n } else {\n throw new Error(\"No path given\");\n }\n }).then(function(path) {\n return host.readFile(path, true).then(function(file) {\n return app.loadFile(file, path);\n });\n });\n })[\"catch\"](function(e) {\n if (e) {\n throw e;\n }\n });\n },\n save: function() {\n var path;\n path = app.currentPath();\n if (path) {\n return app.saveData().then(function(blob) {\n return host.writeFile(path, blob, true);\n }).then(function() {\n app.saved(true);\n return path;\n });\n } else {\n return app.saveAs();\n }\n },\n saveAs: function() {\n return Modal.prompt(\"File Path\", app.currentPath()).then(function(path) {\n if (path) {\n app.currentPath(path);\n return app.save();\n }\n });\n }\n });\n if ((_ref2 = system.config) != null ? _ref2.standalone : void 0) {\n ReaderInput = require(\"../../templates/reader-input\");\n app.open = function() {\n return Modal.show(ReaderInput({\n accept: typeof app.accept === \"function\" ? app.accept() : void 0,\n select: function(file) {\n Modal.hide();\n return app.loadFile(file);\n }\n }));\n };\n app.save = function() {\n return Modal.prompt(\"File name\", \"newfile.txt\").then(function(name) {\n return app.saveData();\n }).then(function(blob) {\n return blob.download();\n });\n };\n }\n Drop(document, function(e) {\n var files;\n if (e.defaultPrevented) {\n return;\n }\n files = e.dataTransfer.files;\n if (files.length) {\n if (app.drop(files)) {\n return e.preventDefault();\n }\n }\n });\n document.addEventListener(\"paste\", function(e) {\n var clipboardData, files;\n if (e.defaultPrevented) {\n return;\n }\n clipboardData = e.clipboardData;\n files = clipboardData.files;\n if (files.length) {\n if (app.paste(files)) {\n return e.preventDefault();\n }\n }\n files = Array.prototype.map.call(e.clipboardData.items, function(item) {\n return item.getAsFile();\n }).filter(function(file) {\n return file;\n });\n if (files.length) {\n if (app.paste(files)) {\n return e.preventDefault();\n }\n }\n });\n TemplateLoader(app);\n try {\n app.loadTemplates(app.pkg);\n } catch (_error) {}\n try {\n app.version = crudeRequire(app.pkg, \"pixie\").version;\n } catch (_error) {}\n app.on(\"boot\", function() {\n var menuBar;\n applyStyle(UI.Style.all, \"base\");\n if (this.style) {\n applyStyle(this.style, \"app\");\n } else {\n try {\n applyStyle(crudeRequire(app.pkg, \"style\"), \"app\");\n } catch (_error) {}\n }\n if (this.menu) {\n menuBar = MenuBar({\n items: this.menu,\n handlers: this\n });\n document.body.appendChild(menuBar.element);\n app.on(\"dispose\", function() {\n menuBar.element.remove();\n return Jadelet.dispose(menuBar.element);\n });\n }\n if (this.element) {\n document.body.appendChild(this.element);\n } else if (this.template) {\n this.element = Jadelet.exec(this.template)(this);\n document.body.appendChild(this.element);\n } else if (this.T.App) {\n this.element = this.T.App(this);\n document.body.appendChild(this.element);\n }\n application.delegate = this;\n if (this.title != null) {\n Observable(function() {\n return application.title(getProp(app, \"title\"));\n });\n }\n if (this.icon != null) {\n Observable(function() {\n return application.icon(getProp(app, \"icon\"));\n });\n }\n if (this.saved != null) {\n return Observable(function() {\n return application.saved(getProp(app, \"saved\"));\n });\n }\n });\n app.on(\"dispose\", function() {\n if (this.element) {\n this.element.remove();\n return Jadelet.dispose(this.element);\n }\n });\n return app;\n };\n};\n\ngetProp = function(context, prop) {\n if (typeof context[prop] === 'function') {\n return context[prop]();\n } else {\n return context[prop];\n }\n};\n"
},
"lib/app/template-loader": {
"content": "var crudeRequire;\n\ncrudeRequire = require(\"../../util\").crudeRequire;\n\nmodule.exports = function(app) {\n return Object.assign(app, {\n loadTemplates: function(pkg) {\n if (app.T == null) {\n app.T = {};\n }\n Object.keys(pkg.distribution).forEach(function(key) {\n var templateName;\n if (key.startsWith('templates/')) {\n templateName = key.replace(/^templates\\//, \"\").replace(/^([a-z])|[_-]([a-z])/g, function(m, a, b) {\n return (a || b).toUpperCase();\n });\n return app.T[templateName] = crudeRequire(pkg, key);\n }\n });\n return app;\n }\n });\n};\n"
},
"lib/assert": {
"content": "module.exports = function(condition, message) {\n if (!condition) {\n throw new Error(message);\n }\n};\n"
},
"lib/aws/cognito": {
"content": "\n/*\nCognito info:\n\nJS SDK: https://github.com/aws/amazon-cognito-identity-js\nPricing: https://aws.amazon.com/cognito/pricing/\nAdding Social Identity Providers: http://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-social.html\n\nhttps://whimsy.auth.us-east-1.amazoncognito.com/oauth2/idpresponse\n */\nmodule.exports = function(_arg) {\n var configureAWSFor, identityPoolId, mapAttributes, poolData, self, userPool;\n identityPoolId = _arg.identityPoolId, poolData = _arg.poolData;\n userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);\n AWS.config.region = 'us-east-1';\n configureAWSFor = function(session, resolve, reject) {\n var loginKey, loginsConfig, token;\n token = session.getIdToken().getJwtToken();\n loginKey = \"cognito-idp.us-east-1.amazonaws.com/\" + poolData.UserPoolId;\n loginsConfig = {};\n loginsConfig[loginKey] = token;\n AWS.config.credentials = new AWS.CognitoIdentityCredentials({\n IdentityPoolId: identityPoolId,\n Logins: loginsConfig\n });\n AWS.config.credentials.refresh(function(error) {\n if (error) {\n reject(error);\n } else {\n resolve(AWS);\n }\n });\n };\n mapAttributes = function(attributes) {\n if (!attributes) {\n return;\n }\n return Object.keys(attributes).map(function(name) {\n var value;\n value = attributes[name];\n return new AWSCognito.CognitoIdentityServiceProvider.CognitoUserAttribute({\n Name: name,\n Value: value\n });\n });\n };\n return self = {\n signUp: function(username, password, attributes) {\n var attributeList;\n attributeList = mapAttributes(attributes);\n return new Promise(function(resolve, reject) {\n return userPool.signUp(username, password, attributeList, null, function(err, result) {\n var cognitoUser;\n if (err) {\n return reject(err);\n }\n cognitoUser = result.user;\n return resolve(cognitoUser);\n });\n });\n },\n authenticate: function(username, password) {\n var authenticationData, authenticationDetails, cognitoUser, userData;\n authenticationData = {\n Username: username,\n Password: password\n };\n authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);\n userData = {\n Username: username,\n Pool: userPool\n };\n cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);\n return new Promise(function(resolve, reject) {\n return cognitoUser.authenticateUser(authenticationDetails, {\n onSuccess: function(session) {\n return configureAWSFor(session, resolve, reject);\n },\n onFailure: reject\n });\n });\n },\n cachedUser: function() {\n return new Promise(function(resolve, reject) {\n var cognitoUser;\n cognitoUser = userPool.getCurrentUser();\n if (cognitoUser) {\n return cognitoUser.getSession(function(err, session) {\n if (err) {\n reject(err);\n return;\n }\n return configureAWSFor(session, resolve, reject);\n });\n } else {\n return setTimeout(function() {\n return reject(new Error(\"No cached user\"));\n });\n }\n });\n },\n logout: function() {\n return Object.keys(localStorage).filter(function(key) {\n return key.match(/^CognitoIdentityServiceProvider/);\n }).forEach(function(key) {\n return delete localStorage[key];\n });\n }\n };\n};\n"
},
"lib/bindable": {
"content": "var remove,\n __slice = [].slice;\n\nmodule.exports = function(I, self) {\n var eventCallbacks;\n if (I == null) {\n I = {};\n }\n if (self == null) {\n self = {};\n }\n eventCallbacks = {};\n Object.assign(self, {\n on: function(event, callback) {\n eventCallbacks[event] || (eventCallbacks[event] = []);\n eventCallbacks[event].push(callback);\n return self;\n },\n off: function(event, callback) {\n if (event) {\n eventCallbacks[event] || (eventCallbacks[event] = []);\n if (callback) {\n remove(eventCallbacks[event], callback);\n } else {\n eventCallbacks[event] = [];\n }\n }\n return self;\n },\n trigger: function() {\n var event, parameters;\n event = arguments[0], parameters = 2 <= arguments.length ? __slice.call(arguments, 1) : [];\n (eventCallbacks[\"*\"] || []).forEach(function(callback) {\n return callback.apply(self, [event].concat(parameters));\n });\n if (event !== \"*\") {\n (eventCallbacks[event] || []).forEach(function(callback) {\n return callback.apply(self, parameters);\n });\n }\n return self;\n }\n });\n return self;\n};\n\nremove = function(array, value) {\n var index;\n index = array.indexOf(value);\n if (index >= 0) {\n return array.splice(index, 1)[0];\n }\n};\n"
},
"lib/drop": {
"content": "module.exports = function(element, handler) {\n var cancel;\n cancel = function(e) {\n e.preventDefault();\n return false;\n };\n element.addEventListener(\"dragover\", cancel);\n element.addEventListener(\"dragenter\", cancel);\n return element.addEventListener(\"drop\", function(e) {\n return handler(e);\n });\n};\n"
},
"lib/extensions": {
"content": "var promiseReader, _base, _base1, _base2;\n\npromiseReader = function(file, method) {\n return new Promise(function(resolve, reject) {\n var reader;\n reader = new FileReader;\n reader.onload = function() {\n return resolve(reader.result);\n };\n reader.onerror = reject;\n return reader[method](file);\n });\n};\n\nBlob.prototype.readAsText = function() {\n return promiseReader(this, \"readAsText\");\n};\n\nBlob.prototype.readAsArrayBuffer = function() {\n return promiseReader(this, \"readAsArrayBuffer\");\n};\n\nBlob.prototype.readAsDataURL = function() {\n return promiseReader(this, \"readAsDataURL\");\n};\n\nif ((_base = Blob.prototype).text == null) {\n _base.text = Blob.prototype.readAsText;\n}\n\nBlob.prototype.getURL = function() {\n return Promise.resolve(URL.createObjectURL(this));\n};\n\nBlob.prototype.readAsJSON = function() {\n return this.readAsText().then(JSON.parse);\n};\n\nBlob.prototype.download = function(path) {\n var a, url;\n url = window.URL.createObjectURL(this);\n a = document.createElement(\"a\");\n a.href = url;\n a.style.display = \"none\";\n a.download = path;\n document.body.appendChild(a);\n a.click();\n window.URL.revokeObjectURL(url);\n return a.remove();\n};\n\nImage.fromBlob = function(blob) {\n return blob.getURL().then(function(url) {\n return new Promise(function(resolve, reject) {\n var img;\n img = new Image;\n img.onload = function() {\n return resolve(img);\n };\n img.onerror = reject;\n return img.src = url;\n });\n });\n};\n\nif (JSON.toBlob == null) {\n JSON.toBlob = function(object, mime) {\n if (mime == null) {\n mime = \"application/json\";\n }\n return new Blob([JSON.stringify(object)], {\n type: \"\" + mime + \"; charset=utf-8\"\n });\n };\n}\n\nif ((_base1 = HTMLCollection.prototype).forEach == null) {\n _base1.forEach = Array.prototype.forEach;\n}\n\nif ((_base2 = FileList.prototype).forEach == null) {\n _base2.forEach = Array.prototype.forEach;\n}\n"
},
"lib/fs/dexie": {
"content": "var Bindable, DexieFSDB, FolderEntry;\n\nBindable = require(\"../bindable\");\n\nFolderEntry = function(path, prefix) {\n return {\n folder: true,\n path: prefix + path,\n relativePath: path\n };\n};\n\nDexieFSDB = function(dbName) {\n var db;\n if (dbName == null) {\n dbName = 'fs';\n }\n db = new Dexie(dbName);\n db.version(1).stores({\n files: 'path, blob, size, type, createdAt, updatedAt'\n });\n return db;\n};\n\nmodule.exports = function(dbName) {\n var Files, db, notify, self;\n if (dbName == null) {\n dbName = 'fs';\n }\n db = DexieFSDB(dbName);\n Files = db.files;\n notify = function(eventType, path) {\n return function(result) {\n self.trigger(eventType, path);\n return result;\n };\n };\n self = {\n read: function(path) {\n return Files.get(path).then(function(result) {\n return result != null ? result.blob : void 0;\n }).then(notify(\"read\", path));\n },\n write: function(path, blob) {\n var now;\n if (!(blob instanceof Blob)) {\n throw new Error(\"Can only write blobs to the file system\");\n }\n now = +(new Date);\n return Files.put({\n path: path,\n blob: blob,\n size: blob.size,\n type: blob.type,\n createdAt: now,\n updatedAt: now\n }).then(notify(\"write\", path));\n },\n \"delete\": function(path) {\n return Files[\"delete\"](path).then(notify(\"delete\", path));\n },\n list: function(dir) {\n return Files.where(\"path\").startsWith(dir).toArray().then(function(files) {\n var folderPaths, folders;\n folderPaths = {};\n files = files.filter(function(file) {\n var folderPath;\n file.relativePath = file.path.replace(dir, \"\");\n if (file.relativePath.match(/\\//)) {\n folderPath = file.relativePath.replace(/\\/.*$/, \"/\");\n folderPaths[folderPath] = true;\n } else {\n return file;\n }\n });\n folders = Object.keys(folderPaths).map(function(folderPath) {\n return FolderEntry(folderPath, dir);\n });\n return folders.concat(files);\n });\n }\n };\n return Bindable(void 0, self);\n};\n"
},
"lib/fs/index": {
"content": "var DexieFS, MountFS, PkgFS, S3FS;\n\nMountFS = require(\"./mount\");\n\nDexieFS = require(\"./dexie\");\n\nPkgFS = require(\"./pkg\");\n\nS3FS = require(\"./s3\");\n\nmodule.exports = {\n Dexie: DexieFS,\n Mount: MountFS,\n Package: PkgFS,\n S3: S3FS\n};\n"
},
"lib/fs/mount": {
"content": "var Bindable,\n __slice = [].slice;\n\nBindable = require(\"../bindable\");\n\nmodule.exports = function(I) {\n var findMountPathFor, longestToShortest, mountPaths, mounts, proxyToMount, self;\n mounts = {};\n mountPaths = [];\n longestToShortest = function(a, b) {\n return b.length - a.length;\n };\n findMountPathFor = function(path) {\n var mountPath;\n mountPath = mountPaths.filter(function(p) {\n return path.startsWith(p);\n })[0];\n return mountPath;\n };\n proxyToMount = function(method) {\n return function() {\n var mount, mountPath, params, path, subsystemPath;\n path = arguments[0], params = 2 <= arguments.length ? __slice.call(arguments, 1) : [];\n mountPath = findMountPathFor(path);\n if (mountPath) {\n mount = mounts[mountPath].subsystem;\n } else {\n throw new Error(\"No mounted filesystem for \" + path);\n }\n subsystemPath = path.replace(mountPath, \"/\");\n if (method === \"list\") {\n return mount[method].apply(mount, [subsystemPath].concat(__slice.call(params))).then(function(entries) {\n return entries.map(function(entry) {\n return Object.assign({}, entry, {\n path: entry.path.replace(\"/\", mountPath)\n });\n });\n });\n } else if (method === \"read\") {\n return mount[method].apply(mount, [subsystemPath].concat(__slice.call(params))).then(function(blob) {\n if (blob) {\n blob.path = path;\n return blob;\n }\n });\n } else {\n return mount[method].apply(mount, [subsystemPath].concat(__slice.call(params)));\n }\n };\n };\n self = {\n read: proxyToMount(\"read\"),\n write: proxyToMount(\"write\"),\n \"delete\": proxyToMount(\"delete\"),\n list: proxyToMount(\"list\"),\n mount: function(folderPath, subsystem) {\n var h, handler, s, _ref;\n handler = function(eventName, path) {\n return self.trigger(eventName, path.replace(\"/\", folderPath));\n };\n if (mounts[folderPath]) {\n _ref = mounts[folderPath], s = _ref.subsystem, h = _ref.handler;\n s.off(\"*\", h);\n }\n subsystem.on(\"*\", handler);\n mounts[folderPath] = {\n subsystem: subsystem,\n handler: handler\n };\n mountPaths.push(folderPath);\n mountPaths.sort(longestToShortest);\n return self;\n }\n };\n Bindable(I, self);\n return self;\n};\n"
},
"lib/fs/pkg": {
"content": "var Bindable, FolderEntry, noop, sourcePath, withoutAllExtensions;\n\nBindable = require(\"../bindable\");\n\nmodule.exports = function(pkg, opts) {\n var compile, compileAndWrite, notify, persist, self;\n if (opts == null) {\n opts = {};\n }\n notify = function(eventType, path) {\n return function(result) {\n self.trigger(eventType, path);\n return result;\n };\n };\n persist = opts.persist || noop;\n compile = opts.compile || function() {\n return Promise.resolve();\n };\n compileAndWrite = function(path, blob) {\n var writeCompiled, writeSource;\n writeSource = blob.readAsText().then(function(text) {\n var srcPath;\n srcPath = sourcePath(path);\n return pkg.source[srcPath] = {\n content: text,\n type: blob.type || \"\",\n path: srcPath\n };\n });\n blob.path = path;\n writeCompiled = compile(blob).then(function(compiledSource) {\n if (typeof compiledSource === \"string\") {\n return pkg.distribution[withoutAllExtensions(sourcePath(path))] = {\n content: compiledSource\n };\n } else {\n return console.warn(\"Can't package files like \" + path + \" yet\", compiledSource);\n }\n });\n return Promise.all([writeSource, writeCompiled]).then(persist);\n };\n self = {\n read: function(path) {\n var blob, content, entry, type;\n entry = pkg.source[sourcePath(path)];\n if (!entry) {\n throw new Error(\"File not found at: \" + path);\n }\n content = entry.content, type = entry.type;\n if (type == null) {\n type = \"\";\n }\n blob = new Blob([content], {\n type: type\n });\n return Promise.resolve(blob).then(notify(\"read\", path));\n },\n write: function(path, blob) {\n return compileAndWrite(path, blob).then(notify(\"write\", path));\n },\n \"delete\": function(path) {\n return Promise.resolve().then(function() {\n return delete pkg.source[sourcePath(path)];\n }).then(notify(\"delete\", path));\n },\n list: function(dir) {\n var sourceDir;\n sourceDir = sourcePath(dir);\n return Promise.resolve().then(function() {\n return Object.keys(pkg.source).filter(function(path) {\n return path.indexOf(sourceDir) === 0;\n }).map(function(path) {\n return {\n path: \"/\" + path,\n relativePath: path.replace(sourceDir, \"\"),\n type: pkg.source[path].type || \"\"\n };\n });\n }).then(function(files) {\n var folderPaths, folders;\n folderPaths = {};\n files = files.filter(function(file) {\n var folderPath;\n if (file.relativePath.match(/\\//)) {\n folderPath = file.relativePath.replace(/\\/.*$/, \"/\");\n folderPaths[folderPath] = true;\n } else {\n return file;\n }\n });\n folders = Object.keys(folderPaths).map(function(folderPath) {\n return FolderEntry(folderPath, dir);\n });\n return folders.concat(files);\n }).then(notify(\"list\", dir));\n }\n };\n return Bindable(void 0, self);\n};\n\nFolderEntry = function(path, prefix) {\n return {\n folder: true,\n path: prefix + path,\n relativePath: path\n };\n};\n\nsourcePath = function(path) {\n return path.replace(/^\\//, \"\");\n};\n\nwithoutAllExtensions = function(path) {\n return path.replace(/\\.[^\\/]*$/, \"\");\n};\n\nnoop = function() {};\n"
},
"lib/fs/s3": {
"content": "var Bindable, blob, delimiter, json, pinvoke, promiseChoke, status,\n __slice = [].slice;\n\nBindable = require(\"../bindable\");\n\npinvoke = function() {\n var method, object, params;\n object = arguments[0], method = arguments[1], params = 3 <= arguments.length ? __slice.call(arguments, 2) : [];\n return new Promise(function(resolve, reject) {\n return object[method].apply(object, __slice.call(params).concat([function(err, result) {\n if (err) {\n reject(err);\n return;\n }\n return resolve(result);\n }]));\n });\n};\n\ndelimiter = \"/\";\n\nstatus = function(response) {\n if (response.status >= 200 && response.status < 300) {\n return response;\n } else {\n throw response;\n }\n};\n\njson = function(response) {\n return response.json();\n};\n\nblob = function(response) {\n return response.blob();\n};\n\npromiseChoke = function(fn) {\n var cache;\n cache = {};\n return function(key) {\n var cached;\n cached = cache[key];\n if (cached) {\n return cached;\n }\n return cache[key] = fn(key)[\"finally\"](function() {\n return delete cache[key];\n });\n };\n};\n\nmodule.exports = function(id, bucket, refreshCredentials) {\n var BlobSham, FileEntry, FolderEntry, deleteFromS3, fetchFileMeta, fetchMeta, getRemote, list, localCache, metaCache, notify, refreshCredentialsPromise, self, uploadToS3;\n if (refreshCredentials == null) {\n refreshCredentials = function() {\n return Promise.reject(new Error(\"No method given to refresh credentials automatically\"));\n };\n }\n refreshCredentialsPromise = Promise.resolve();\n (function(oldPromiseInvoke) {\n return pinvoke = function() {\n var args;\n args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n return refreshCredentialsPromise.then(function() {\n return oldPromiseInvoke.apply(null, args);\n })[\"catch\"](function(e) {\n if (e.code === \"CredentialsError\") {\n console.info(\"Refreshing credentials after CredentialsError\", e);\n refreshCredentialsPromise = refreshCredentials();\n return refreshCredentialsPromise.then(function() {\n return oldPromiseInvoke.apply(null, args);\n });\n } else {\n throw e;\n }\n });\n };\n })(pinvoke);\n localCache = {};\n metaCache = {};\n uploadToS3 = function(bucket, key, file, options) {\n var cacheControl;\n if (options == null) {\n options = {};\n }\n cacheControl = options.cacheControl;\n if (cacheControl == null) {\n cacheControl = 0;\n }\n localCache[key] = file;\n metaCache[key] = {\n ContentType: file.type,\n UpdatedAt: new Date\n };\n return pinvoke(bucket, \"putObject\", {\n Key: key,\n ContentType: file.type,\n Body: file,\n CacheControl: \"max-age=\" + cacheControl\n });\n };\n getRemote = function(bucket, key) {\n var cachedItem;\n cachedItem = localCache[key];\n if (cachedItem) {\n if (cachedItem instanceof Blob) {\n return Promise.resolve(cachedItem);\n } else {\n return Promise.reject(cachedItem);\n }\n }\n return pinvoke(bucket, \"getObject\", {\n Key: key\n }).then(function(data) {\n var Body, ContentType;\n Body = data.Body, ContentType = data.ContentType;\n return new Blob([Body], {\n type: ContentType\n });\n }).then(function(data) {\n return localCache[key] = data;\n })[\"catch\"](function(e) {\n localCache[key] = e;\n throw e;\n });\n };\n deleteFromS3 = function(bucket, key) {\n localCache[key] = new Error(\"Not Found\");\n delete metaCache[key];\n return pinvoke(bucket, \"deleteObject\", {\n Key: key\n });\n };\n list = promiseChoke(function(dir) {\n var prefix;\n prefix = \"\" + id + dir;\n return pinvoke(bucket, \"listObjects\", {\n Prefix: prefix,\n Delimiter: delimiter\n }).then(function(result) {\n var results;\n results = result.CommonPrefixes.map(function(p) {\n return FolderEntry(p.Prefix, id, prefix);\n }).concat(result.Contents.map(function(o) {\n return FileEntry(o, id, prefix, bucket);\n })).map(function(entry) {\n return fetchMeta(entry, bucket);\n });\n return Promise.all(results);\n });\n });\n fetchFileMeta = function(key, bucket) {\n var cachedItem;\n cachedItem = metaCache[key];\n if (cachedItem) {\n return Promise.resolve(cachedItem);\n }\n return pinvoke(bucket, \"headObject\", {\n Key: key\n }).then(function(result) {\n metaCache[key] = result;\n return result;\n });\n };\n fetchMeta = function(entry, bucket) {\n return Promise.resolve().then(function() {\n if (entry.folder) {\n return entry;\n }\n return fetchFileMeta(entry.remotePath, bucket).then(function(meta) {\n entry.type = meta.ContentType;\n entry.updatedAt = new Date(meta.LastModified);\n return entry;\n });\n });\n };\n notify = function(eventType, path) {\n return function(result) {\n self.trigger(eventType, path);\n return result;\n };\n };\n FolderEntry = function(path, id, prefix) {\n return {\n folder: true,\n path: path.replace(id, \"\"),\n relativePath: path.replace(prefix, \"\"),\n remotePath: path\n };\n };\n FileEntry = function(object, id, prefix, bucket) {\n var entry, path;\n path = object.Key;\n entry = {\n path: path.replace(id, \"\"),\n relativePath: path.replace(prefix, \"\"),\n remotePath: path,\n size: object.Size\n };\n entry.blob = BlobSham(entry, bucket);\n return entry;\n };\n BlobSham = function(entry, bucket) {\n var remotePath, urlPromise;\n remotePath = entry.remotePath;\n urlPromise = null;\n return {\n getURL: function() {\n if (urlPromise) {\n return urlPromise;\n }\n return urlPromise = getRemote(bucket, remotePath).then(function(blob) {\n return URL.createObjectURL(blob);\n });\n },\n readAsText: function() {\n return getRemote(bucket, remotePath).then(function(blob) {\n return blob.readAsText();\n });\n }\n };\n };\n return self = Object.assign(Bindable(), {\n read: function(path) {\n var key;\n if (!path.startsWith(delimiter)) {\n path = delimiter + path;\n }\n key = \"\" + id + path;\n return getRemote(bucket, key).then(notify(\"read\", path));\n },\n write: function(path, blob, options) {\n var key;\n if (!path.startsWith(delimiter)) {\n path = delimiter + path;\n }\n key = \"\" + id + path;\n return uploadToS3(bucket, key, blob, options).then(notify(\"write\", path));\n },\n \"delete\": function(path) {\n var key;\n if (!path.startsWith(delimiter)) {\n path = delimiter + path;\n }\n key = \"\" + id + path;\n return deleteFromS3(bucket, key).then(notify(\"delete\", path));\n },\n list: function(dir) {\n if (dir == null) {\n dir = \"/\";\n }\n if (!dir.startsWith(delimiter)) {\n dir = \"\" + delimiter + dir;\n }\n if (!dir.endsWith(delimiter)) {\n dir = \"\" + dir + delimiter;\n }\n return list(dir).then(notify(\"list\", dir));\n }\n });\n};\n"
},
"lib/indent-parse": {
"content": "var parse, top;\n\ntop = function(stack) {\n return stack[stack.length - 1];\n};\n\nparse = function(source) {\n var depth, indentation, stack;\n stack = [[]];\n indentation = /^( )*/;\n depth = 0;\n source.split(\"\\n\").forEach(function(line, lineNumber) {\n var current, items, match, newDepth, prev, text;\n match = line.match(indentation)[0];\n text = line.replace(match, \"\");\n newDepth = match.length / 2;\n if (!text.trim().length) {\n return;\n }\n current = text;\n if (newDepth > depth) {\n if (newDepth !== depth + 1) {\n throw new Error(\"Unexpected indentation on line \" + lineNumber);\n }\n items = [];\n prev = top(stack);\n prev.push([prev.pop(), items]);\n stack.push(items);\n } else if (newDepth < depth) {\n stack = stack.slice(0, newDepth + 1);\n }\n depth = newDepth;\n return top(stack).push(current);\n });\n return stack[0];\n};\n\nmodule.exports = parse;\n"
},
"lib/jadelet-parser": {
"content": "(function(create, rules) {\n create(create, rules);\n}(function(create, rules) {\n var cached, cachedFn, decompile, fail, failExpected, failIndex, fns, generate, hToS, invoke, loc, mapResult, mapValue, maxFailPos, noteName, parse, preComputedRules, precompute, precomputeRule, prettyPrint, sequenceFn, toS, validate, _names;\n failExpected = Array(16);\n failIndex = 0;\n maxFailPos = 0;\n fail = function(pos, expected) {\n if (pos < maxFailPos) {\n return;\n }\n if (pos > maxFailPos) {\n maxFailPos = pos;\n failIndex = 0;\n }\n failExpected[failIndex++] = expected;\n };\n prettyPrint = function(v) {\n var name, pv;\n pv = v instanceof RegExp ? v.toString().slice(0, -3) : typeof v === \"string\" ? v === \"\" ? \"EOF\" : JSON.stringify(v) : v;\n if (name = _names.get(v)) {\n return \"\" + name + \" \" + pv;\n } else {\n return pv;\n }\n };\n _names = new Map;\n noteName = function(name, value) {\n if (name) {\n _names.set(value, name);\n }\n return value;\n };\n preComputedRules = {};\n precomputeRule = function(r, out, name) {\n var data, placeholder, result;\n if (Array.isArray(r)) {\n result = r.map(function(p, i) {\n var fn;\n if (i === 0) {\n fn = fns[p];\n if (!fn) {\n throw new Error(\"No primitive for \" + (JSON.stringify(p)) + \", possible options: \" + (Object.keys(fns)));\n }\n return fn;\n } else if (i === 1) {\n switch (r[0]) {\n case \"/\":\n case \"S\":\n return p.map(function(x) {\n return precomputeRule(x, null, name);\n });\n case \"*\":\n case \"+\":\n case \"?\":\n case \"!\":\n case \"&\":\n return precomputeRule(p, null, name);\n case \"R\":\n return noteName(name, RegExp(p, \"uy\"));\n case \"L\":\n return noteName(name, JSON.parse(\"\\\"\" + p + \"\\\"\"));\n default:\n throw new Error(\"Don't know how to pre-compute \" + (JSON.stringify(r[0])));\n }\n } else {\n if (p.f) {\n return {\n fn: cachedFn(p.f)\n };\n } else {\n return p;\n }\n }\n });\n if (out) {\n out.splice.apply(out, [0, 0].concat(result));\n return out;\n }\n return result;\n } else {\n if (preComputedRules[r]) {\n return preComputedRules[r];\n } else {\n preComputedRules[r] = placeholder = [];\n data = rules[r];\n if (data == null) {\n throw new Error(\"No rule with name \" + (JSON.stringify(r)));\n }\n return precomputeRule(data, placeholder, r);\n }\n }\n };\n precompute = function(rules) {\n var first;\n first = Object.keys(rules)[0];\n preComputedRules[first] = precomputeRule(first);\n return preComputedRules;\n };\n cached = function(fn) {\n var cache, f;\n cache = new Map;\n f = function(key) {\n var result;\n result = cache.get(key);\n if (result) {\n return result;\n }\n result = fn.apply(null, arguments);\n cache.set(key, result);\n return result;\n };\n f.cache = cache;\n return f;\n };\n cachedFn = cached(function(body) {\n return new Function(\"$1\", \"$2\", \"$3\", \"$4\", \"$5\", \"$6\", \"$7\", \"$8\", \"$9\", body);\n });\n invoke = function(state, data) {\n var arg, fn, mapping, result;\n fn = data[0], arg = data[1];\n result = fn(state, arg);\n if (result && ((mapping = data[2]) != null)) {\n return mapResult(mapping, result, fn === sequenceFn);\n } else {\n return result;\n }\n };\n mapValue = function(mapping, value, mode) {\n switch (typeof mapping) {\n case \"number\":\n return value[mapping];\n case \"string\":\n return mapping;\n case \"object\":\n if (Array.isArray(mapping)) {\n return mapping.map(function(n) {\n return mapValue(n, value);\n });\n } else {\n return mapping.fn[mode](null, value);\n }\n break;\n default:\n throw new Error(\"Unknown mapping type\");\n }\n };\n mapResult = function(mapping, result, isSequence) {\n var mode;\n if (isSequence) {\n mode = \"apply\";\n } else {\n mode = \"call\";\n }\n result.value = mapValue(mapping, result.value, mode);\n return result;\n };\n fns = {\n L: function(state, str) {\n var input, length, pos;\n input = state.input, pos = state.pos;\n length = str.length;\n if (input.substr(pos, length) === str) {\n return {\n pos: pos + length,\n value: str\n };\n } else {\n return fail(pos, str);\n }\n },\n R: function(state, regExp) {\n var input, m, pos, v;\n input = state.input, pos = state.pos;\n regExp.lastIndex = state.pos;\n if (m = input.match(regExp)) {\n v = m[0];\n }\n if (v != null) {\n return {\n pos: pos + v.length,\n value: v\n };\n } else {\n return fail(pos, regExp);\n }\n },\n S: function(state, terms) {\n var i, input, l, pos, r, results, value;\n input = state.input, pos = state.pos;\n results = [];\n i = 0;\n l = terms.length;\n while (true) {\n r = invoke({\n input: input,\n pos: pos\n }, terms[i++]);\n if (r) {\n pos = r.pos, value = r.value;\n results.push(value);\n } else {\n return;\n }\n if (i >= l) {\n break;\n }\n }\n return {\n pos: pos,\n value: results\n };\n },\n \"/\": function(state, terms) {\n var i, l, r;\n i = 0;\n l = terms.length;\n while (true) {\n r = invoke(state, terms[i++]);\n if (r) {\n return r;\n }\n if (i >= l) {\n break;\n }\n }\n },\n \"?\": function(state, term) {\n var result;\n result = invoke(state, term);\n if (result) {\n return result;\n } else {\n return state;\n }\n },\n \"*\": function(state, term) {\n var input, pos, prevPos, r, results, value;\n input = state.input, pos = state.pos;\n results = [];\n while (true) {\n prevPos = pos;\n r = invoke({\n input: input,\n pos: pos\n }, term);\n if (r == null) {\n break;\n }\n pos = r.pos, value = r.value;\n if (pos === prevPos) {\n break;\n } else {\n results.push(value);\n }\n }\n return {\n pos: pos,\n value: results\n };\n },\n \"+\": function(state, term) {\n var first, input, pos, rest;\n input = state.input;\n first = invoke(state, term);\n if (first == null) {\n return;\n }\n pos = first.pos;\n pos = (rest = invoke({\n input: input,\n pos: pos\n }, [fns[\"*\"], term])).pos;\n rest.value.unshift(first.value);\n return {\n value: rest.value,\n pos: pos\n };\n },\n \"!\": function(state, term) {\n var newState;\n newState = invoke(state, term);\n if (newState != null) {\n\n } else {\n return state;\n }\n },\n \"&\": function(state, term) {\n var newState;\n newState = invoke(state, term);\n if (newState.pos === state.pos) {\n\n } else {\n return state;\n }\n }\n };\n sequenceFn = fns.S;\n loc = function(input, pos) {\n var column, line, rawPos, _ref;\n rawPos = pos;\n _ref = input.split(/\\n|\\r\\n|\\r/).reduce(function(_arg, line) {\n var col, l, row;\n row = _arg[0], col = _arg[1];\n l = line.length + 1;\n if (pos > l) {\n pos -= l;\n return [row + 1, 1];\n } else if (pos >= 0) {\n col += pos;\n pos = -1;\n return [row, col];\n } else {\n return [row, col];\n }\n }, [1, 1]), line = _ref[0], column = _ref[1];\n return \"\" + line + \":\" + column;\n };\n validate = function(input, result, _arg) {\n var expectations, filename, l;\n filename = _arg.filename;\n if ((result != null) && result.pos === input.length) {\n return result.value;\n }\n expectations = Array.from(new Set(failExpected.slice(0, failIndex)));\n l = loc(input, maxFailPos);\n if (expectations.length) {\n throw new Error(\"\" + filename + \":\" + l + \" Failed to parse\\nExpected:\\n\\t\" + (expectations.map(prettyPrint).join(\"\\n\\t\")) + \"\\nFound: \" + (prettyPrint(input.substr(maxFailPos, 1))));\n } else {\n throw new Error(\"Unconsumed input at \" + l + \"\\n\\n\" + (input.slice(result.pos)) + \"\\n\");\n }\n };\n parse = function(input, opts) {\n var result, state;\n if (opts == null) {\n opts = {};\n }\n if (typeof input !== \"string\") {\n throw new Error(\"Input must be a string\");\n }\n if (opts.filename == null) {\n opts.filename = \"[stdin]\";\n }\n failIndex = 0;\n maxFailPos = 0;\n state = {\n input: input,\n pos: 0\n };\n result = invoke(state, Object.values(preComputedRules)[0]);\n return validate(input, result, opts);\n };\n generate = function(rules, vivify) {\n var m, src;\n src = \"(function(create, rules) {\\n create(create, rules);\\n}(\" + (create.toString()) + \", \" + (JSON.stringify(rules)) + \"));\\n\";\n if (vivify) {\n m = {};\n Function(\"module\", src)(m);\n return m.exports;\n } else {\n return src;\n }\n };\n hToS = function(h) {\n if (h == null) {\n return \"\";\n }\n return \" -> \" + (function() {\n switch (typeof h) {\n case \"number\":\n return h;\n case \"string\":\n return JSON.stringify(h);\n case \"object\":\n if (Array.isArray(h)) {\n return JSON.stringify(h);\n } else {\n return \"\\n\" + (h.f.replace(/^|\\n/g, \"$& \"));\n }\n }\n })();\n };\n toS = function(rule, depth) {\n var f, h, terms;\n if (depth == null) {\n depth = 0;\n }\n if (Array.isArray(rule)) {\n f = rule[0];\n h = rule[2];\n switch (f) {\n case \"*\":\n case \"+\":\n case \"?\":\n return toS(rule[1], depth + 1) + f + hToS(h);\n case \"&\":\n case \"!\":\n return f + toS(rule[1], depth + 1);\n case \"L\":\n return '\"' + rule[1] + '\"' + hToS(h);\n case \"R\":\n return '/' + rule[1] + '/' + hToS(h);\n case \"S\":\n terms = rule[1].map(function(i) {\n return toS(i, depth + 1);\n });\n if (depth < 1) {\n return terms.join(\" \") + hToS(h);\n } else {\n return \"( \" + terms.join(\" \") + \" )\";\n }\n break;\n case \"/\":\n terms = rule[1].map(function(i) {\n return toS(i, depth && depth + 1);\n });\n if (depth === 0) {\n return terms.join(\"\\n \");\n } else {\n return \"( \" + terms.join(\" / \") + \" )\";\n }\n }\n } else {\n return rule;\n }\n };\n decompile = function(rules) {\n return Object.keys(rules).map(function(name) {\n var value;\n value = toS(rules[name]);\n return \"\" + name + \"\\n \" + value + \"\\n\";\n }).join(\"\\n\");\n };\n precompute(rules);\n return module.exports = {\n decompile: decompile,\n parse: parse,\n generate: generate,\n rules: rules\n };\n}, {\"Template\":[\"S\",[[\"?\",\"__\"],[\"*\",\"Line\"]],{\"f\":\"var top = a => a[a.length-1];\\nfunction reduceLines(lines) {\\n var depth = 0;\\n var stack = [[]];\\n lines.forEach( ([indent, line]) => {\\n if (Array.isArray(line)) {\\n line[1] = collectAttributes(line[1])\\n }\\n if (depth+1 === indent) {\\n // We're adding to the content of the last element in the current stack\\n stack.push(top(top(stack))[2])\\n } else if ( indent > depth) {\\n throw new Error(\\\"Indented too far\\\")\\n } else if (indent < depth) {\\n stack = stack.slice(0, indent + 1)\\n }\\n depth = indent\\n top(stack).push(line)\\n })\\n return stack[0]\\n}\\nfunction collectAttributes(attributesArray) {\\n return attributesArray.reduce((o, [key, value]) => {\\n if (key === \\\"id\\\" || key === \\\"class\\\" || key === \\\"style\\\") {\\n var p = o[key] || (o[key] = [])\\n p.push(value)\\n } else {\\n o[key] = value\\n }\\n return o\\n }, {})\\n}\\nfunction pretty(lines) {\\n return lines.map(line =>\\n JSON.stringify(line)\\n )\\n}\\nvar reduced = reduceLines($2);\\nif (reduced.length != 1) {\\n throw new Error(\\\"Must have exactly one root node.\\\");\\n}\\nreturn reduced[0];\"}],\"Line\":[\"S\",[\"Indent\",\"LineBody\",\"EOS\"],[0,1]],\"LineBody\":[\"/\",[[\"S\",[\"Tag\",[\"?\",\"DeprecatedEquals\"],\"_\",\"RestOfLine\"],{\"f\":\"$1[2].push($4)\\nreturn $1\"}],[\"S\",[\"Tag\",[\"?\",\"_\"]],0],[\"S\",[[\"L\",\"|\"],[\"?\",[\"L\",\" \"]],\"RestOfLine\"],{\"f\":\"return $3 + \\\"\\\\n\\\";\"}],[\"S\",[[\"?\",[\"S\",[\"DeprecatedEquals\",\"_\"]]],\"RestOfLine\"],1]]],\"RestOfLine\":[\"R\",\"[^\\\\n\\\\r]*\",{\"f\":\"// TODO: Handle runs of text with bound content inside\\nif ($1.slice(0,1) === \\\"@\\\") {\\n return {\\n bind: $1.slice(1)\\n }\\n} else {\\n return $1\\n}\"}],\"DeprecatedEquals\":[\"L\",\"=\",{\"f\":\"console.warn(\\\"'= <content>' is deprecated, you can remove the '=' without issue.\\\")\"}],\"Tag\":[\"/\",[[\"S\",[\"TagName\",\"OptionalIds\",\"OptionalClasses\",\"OptionalAttributes\"],{\"f\":\"return [\\n $1,\\n $2.concat($3, $4),\\n [],\\n]\"}],[\"S\",[\"Ids\",\"OptionalClasses\",\"OptionalAttributes\"],{\"f\":\"return [\\n \\\"div\\\",\\n $1.concat($2, $3),\\n [],\\n]\"}],[\"S\",[\"Classes\",\"OptionalAttributes\"],{\"f\":\"return [\\n \\\"div\\\",\\n $1.concat($2),\\n [],\\n]\"}]]],\"OptionalClasses\":[\"/\",[\"Classes\",[\"L\",\"\",{\"f\":\"return []\"}]]],\"Classes\":[\"+\",\"Class\"],\"Class\":[\"/\",[[\"S\",[[\"L\",\".\"],\"Identifier\"],{\"f\":\"return [\\\"class\\\", $2]\"}],[\"S\",[[\"L\",\".\"],[\"!\",\"Identifier\"]],{\"f\":\"throw \\\"Expected a class name\\\"\"}],\"IdError\"]],\"OptionalIds\":[\"?\",\"Ids\",{\"f\":\"return $1 || []\"}],\"Ids\":[\"S\",[\"Id\"],{\"f\":\"return [ $1 ]\"}],\"Id\":[\"/\",[[\"S\",[[\"L\",\"#\"],\"Identifier\"],{\"f\":\"return [\\\"id\\\", $2]\"}],[\"S\",[[\"L\",\"#\"],[\"!\",\"Identifier\"]],{\"f\":\"throw \\\"Expected an id name\\\"\"}]]],\"IdError\":[\"L\",\"#\",{\"f\":\"throw \\\"Ids must appear before classes and attributes. Elements can only have one id.\\\"\"}],\"ClassError\":[\"L\",\".\",{\"f\":\"throw \\\"Classes cannot appear after attributes.\\\"\"}],\"TagName\":\"Identifier\",\"OptionalAttributes\":[\"/\",[[\"S\",[[\"L\",\"(\"],[\"?\",\"__\"],[\"+\",\"Attribute\"],[\"L\",\")\"],[\"?\",\"IdError\"],[\"?\",\"ClassError\"]],{\"f\":\"return $3\"}],[\"L\",\"(\",{\"f\":\"throw \\\"Invalid attributes\\\"\"}],[\"L\",\"\",{\"f\":\"return []\"}]]],\"Attribute\":[\"/\",[[\"S\",[\"AtIdentifier\",[\"?\",\"__\"]],{\"f\":\"return [$1.bind, $1]\"}],[\"S\",[\"EqBinding\",[\"?\",\"__\"]],0],[\"S\",[\"Identifier\",[\"?\",\"__\"]],{\"f\":\"return [$1, \\\"\\\"]\"}]]],\"AtIdentifier\":[\"S\",[[\"L\",\"@\"],\"Identifier\"],{\"f\":\"return {\\n bind: $2\\n}\"}],\"EqBinding\":[\"S\",[\"Identifier\",[\"L\",\"=\"],[\"/\",[\"AtIdentifier\",\"Value\"]]],[0,2]],\"Identifier\":[\"R\",\"[a-zA-Z][a-zA-Z0-9-]*\"],\"Indent\":[\"*\",[\"/\",[[\"L\",\" \"],[\"L\",\"\\\\t\"]]],{\"f\":\"return $1.length\"}],\"_\":[\"R\",\"[ \\\\t]+\"],\"__\":[\"+\",[\"/\",[[\"R\",\"[ \\\\t]\"],\"EOL\"]]],\"Value\":[\"/\",[[\"S\",[[\"L\",\"\\\\\\\"\"],[\"*\",\"DoubleStringCharacter\"],[\"L\",\"\\\\\\\"\"]],{\"f\":\"return $2.join(\\\"\\\")\"}],[\"S\",[[\"L\",\"'\"],[\"*\",\"SingleStringCharacter\"],[\"L\",\"'\"]],{\"f\":\"return $2.join(\\\"\\\")\"}],\"Number\"]],\"DoubleStringCharacter\":[\"/\",[[\"S\",[[\"!\",[\"/\",[[\"L\",\"\\\\\\\"\"],[\"L\",\"\\\\\\\\\"]]]],[\"R\",\".\"]],1],[\"S\",[[\"L\",\"\\\\\\\\\"],\"EscapeSequence\"],1]]],\"SingleStringCharacter\":[\"/\",[[\"S\",[[\"!\",[\"/\",[[\"L\",\"'\"],[\"L\",\"\\\\\\\\\"]]]],[\"R\",\".\"]],1],[\"S\",[[\"L\",\"\\\\\\\\\"],\"EscapeSequence\"],1]]],\"EscapeSequence\":[\"/\",[[\"L\",\"'\"],[\"L\",\"\\\\\\\"\"],[\"L\",\"\\\\\\\\\"],[\"R\",\".\",{\"f\":\"return \\\"\\\\\\\\\\\" + $1\"}]]],\"Number\":[\"/\",[[\"R\",\"-?[0-9]+\\\\.[0-9]+\"],[\"R\",\"-?[0-9]+\"]]],\"EOS\":[\"/\",[[\"S\",[[\"+\",[\"S\",[[\"?\",\"_\"],\"EOL\"]]],\"_\",\"EOF\"]],[\"+\",[\"S\",[[\"?\",\"_\"],\"EOL\"]]],\"EOF\"]],\"EOL\":[\"/\",[[\"L\",\"\\\\r\\\\n\"],[\"L\",\"\\\\n\"],[\"L\",\"\\\\r\"]]],\"EOF\":[\"!\",[\"R\",\"[\\\\s\\\\S]\"]]}));\n"
},
"lib/jadelet": {
"content": "var Jadelet, Observable, append, attachCleaner, bindEvent, bindObservable, bindSplat, bindValue, createElement, dispose, elementCleaners, elementRefCounts, eventNames, forEach, get, isEvent, isObject, isString, observeAttribute, observeContent, parser, release, remove, render, retain, splat;\n\nObservable = require(\"./observable\");\n\nforEach = Array.prototype.forEach;\n\nelementCleaners = new WeakMap;\n\nelementRefCounts = new WeakMap;\n\nretain = function(element) {\n var count;\n count = elementRefCounts.get(element) || 0;\n elementRefCounts.set(element, count + 1);\n};\n\nrelease = function(element) {\n var count;\n count = elementRefCounts.get(element) || 0;\n count--;\n if (count > 0) {\n elementRefCounts.set(element, count);\n } else {\n elementRefCounts[\"delete\"](element);\n dispose(element);\n }\n};\n\ndispose = function(element) {\n var children, _ref;\n children = element.children;\n if (children != null) {\n forEach.call(children, dispose);\n }\n if ((_ref = elementCleaners.get(element)) != null) {\n _ref.forEach(function(cleaner) {\n cleaner();\n elementCleaners[\"delete\"](element);\n });\n }\n};\n\nattachCleaner = function(element, cleaner) {\n var cleaners;\n if (typeof cleaner !== 'function') {\n throw new Error(\"whoops\");\n }\n cleaners = elementCleaners.get(element);\n if (cleaners) {\n cleaners.push(cleaner);\n } else {\n elementCleaners.set(element, [cleaner]);\n }\n};\n\neventNames = /^on(touch|animation|transition)(start|iteration|move|end|cancel)$/;\n\nisEvent = function(name, element) {\n return name.match(eventNames) || name in element;\n};\n\nobserveAttribute = function(element, context, name, value) {\n var bind;\n switch (name) {\n case \"id\":\n bindSplat(element, context, value, function(ids) {\n var length;\n length = ids.length;\n if (length) {\n element.id = ids[length - 1];\n } else {\n element.removeAttribute(\"id\");\n }\n });\n break;\n case \"class\":\n bindSplat(element, context, value, function(classes) {\n var className;\n className = classes.join(\" \");\n if (className) {\n element.className = className;\n } else {\n element.removeAttribute(\"class\");\n }\n });\n break;\n case \"style\":\n bindSplat(element, context, value, function(styles) {\n element.removeAttribute(\"style\");\n styles.forEach(function(style) {\n if (isObject(style)) {\n return Object.assign(element.style, style);\n } else {\n return element.setAttribute(\"style\", style);\n }\n });\n });\n break;\n case \"value\":\n bindValue(element, value, context);\n break;\n case \"checked\":\n if (value && isObject(value)) {\n bind = value.bind;\n element.onchange = function() {\n if (typeof context[bind] === \"function\") {\n context[bind](element.checked);\n }\n };\n }\n bindObservable(element, value, context, function(newValue) {\n element.checked = newValue;\n });\n break;\n default:\n if (isEvent(\"on\" + name, element)) {\n bindEvent(element, name, value.bind, context);\n } else {\n bindObservable(element, value, context, function(newValue) {\n if ((newValue != null) && newValue !== false) {\n element.setAttribute(name, newValue);\n } else {\n element.removeAttribute(name);\n }\n });\n }\n }\n};\n\nbindObservable = function(element, value, context, update) {\n var bind, observable;\n if (isString(value)) {\n return update(value);\n } else if (typeof value === 'function') {\n observable = Observable(function() {\n update(value.call(context));\n });\n } else {\n bind = value.bind;\n observable = Observable(function() {\n update(get(context[bind], context));\n });\n }\n if (observable._observableDependencies.size === 0) {\n return;\n }\n attachCleaner(element, observable.releaseDependencies);\n};\n\nbindValue = function(element, value, context) {\n var bind;\n if (value && typeof value === \"object\") {\n bind = value.bind;\n element.oninput = element.onchange = function() {\n if (typeof context[bind] === \"function\") {\n context[bind](element.value);\n }\n };\n }\n bindObservable(element, value, context, function(newValue) {\n if (element.value !== newValue) {\n element.value = newValue;\n }\n });\n};\n\nbindEvent = function(element, name, binding, context) {\n var handler;\n handler = context[binding];\n if (typeof handler === 'function') {\n element.addEventListener(name, handler.bind(context));\n }\n};\n\nbindSplat = function(element, context, sources, update) {\n bindObservable(element, (function() {\n return splat(sources, context);\n }), context, update);\n};\n\nobserveContent = function(element, context, contentArray) {\n var count, tracker;\n tracker = [];\n count = 0;\n contentArray.forEach(function(astNode, index) {\n var length, previousLength;\n tracker[index] = count;\n if (Array.isArray(astNode)) {\n element.appendChild(render(astNode, context));\n count++;\n } else if (isString(astNode)) {\n element.appendChild(document.createTextNode(astNode));\n count++;\n } else if (isObject(astNode)) {\n length = previousLength = 0;\n bindObservable(element, astNode, context, function(value) {\n var beforeTarget, delta, i, pos;\n previousLength = length;\n pos = tracker[index];\n beforeTarget = element.childNodes[pos + length];\n i = 0;\n while (i < length) {\n remove(element, element.childNodes[pos]);\n i++;\n }\n length = append(element, value, beforeTarget);\n delta = length - previousLength;\n i = index + 1;\n while (i < tracker.length) {\n tracker[i] += delta;\n i++;\n }\n });\n count += length;\n } else {\n throw new Error(\"oof\");\n }\n });\n};\n\ncreateElement = function(tag) {\n return document.createElement(tag);\n};\n\nappend = function(element, item, beforeTarget) {\n if (item == null) {\n return 0;\n } else if (Array.isArray(item)) {\n return item.map(function(item) {\n return append(element, item, beforeTarget);\n }).reduce(function(a, b) {\n return a + b;\n }, 0);\n } else if (item instanceof Node) {\n retain(item);\n element.insertBefore(item, beforeTarget);\n } else {\n element.insertBefore(document.createTextNode(item), beforeTarget);\n }\n return 1;\n};\n\nremove = function(element, child) {\n element.removeChild(child);\n return release(child);\n};\n\nisObject = function(x) {\n return typeof x === \"object\";\n};\n\nisString = function(x) {\n return typeof x === \"string\";\n};\n\nsplat = function(sources, context) {\n return sources.map(function(source) {\n if (isString(source)) {\n return source;\n } else {\n return get(context[source.bind], context);\n }\n }).reduce(function(a, b) {\n return a.concat(b);\n }, []).filter(function(x) {\n return x != null;\n });\n};\n\nget = function(x, context) {\n if (typeof x === 'function') {\n return x.call(context);\n } else {\n return x;\n }\n};\n\nrender = function(astNode, context) {\n var attributes, children, element, tag;\n if (context == null) {\n context = {};\n }\n tag = astNode[0], attributes = astNode[1], children = astNode[2];\n element = createElement(tag);\n observeContent(element, context, children);\n Object.keys(attributes).forEach(function(name) {\n observeAttribute(element, context, name, attributes[name]);\n });\n return element;\n};\n\nparser = require(\"./jadelet-parser\");\n\nmodule.exports = Jadelet = {\n compile: function(source, opts) {\n var ast, exports, runtime;\n if (opts == null) {\n opts = {};\n }\n ast = Jadelet.parse(source);\n runtime = opts.runtime || \"system.ui.Jadelet\";\n exports = opts.exports || \"module.exports\";\n return \"\" + exports + \" = \" + runtime + \".exec(\" + (JSON.stringify(ast)) + \");\";\n },\n parse: parser.parse,\n exec: function(ast) {\n if (typeof ast === \"function\") {\n return ast;\n }\n if (typeof ast === \"string\") {\n ast = Jadelet.parse(ast);\n }\n return function(context) {\n return render(ast, context);\n };\n },\n Observable: Observable,\n _elementCleaners: elementCleaners,\n dispose: dispose,\n retain: retain,\n release: release\n};\n"
},
"lib/mousetrap": {
"content": "/* mousetrap v1.6.3 craig.is/killing/mice */\r\n(function(q,u,c){function v(a,b,g){a.addEventListener?a.addEventListener(b,g,!1):a.attachEvent(\"on\"+b,g)}function z(a){if(\"keypress\"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return n[a.which]?n[a.which]:r[a.which]?r[a.which]:String.fromCharCode(a.which).toLowerCase()}function F(a){var b=[];a.shiftKey&&b.push(\"shift\");a.altKey&&b.push(\"alt\");a.ctrlKey&&b.push(\"ctrl\");a.metaKey&&b.push(\"meta\");return b}function w(a){return\"shift\"==a||\"ctrl\"==a||\"alt\"==a||\r\n\"meta\"==a}function A(a,b){var g,d=[];var e=a;\"+\"===e?e=[\"+\"]:(e=e.replace(/\\+{2}/g,\"+plus\"),e=e.split(\"+\"));for(g=0;g<e.length;++g){var m=e[g];B[m]&&(m=B[m]);b&&\"keypress\"!=b&&C[m]&&(m=C[m],d.push(\"shift\"));w(m)&&d.push(m)}e=m;g=b;if(!g){if(!p){p={};for(var c in n)95<c&&112>c||n.hasOwnProperty(c)&&(p[n[c]]=c)}g=p[e]?\"keydown\":\"keypress\"}\"keypress\"==g&&d.length&&(g=\"keydown\");return{key:m,modifiers:d,action:g}}function D(a,b){return null===a||a===u?!1:a===b?!0:D(a.parentNode,b)}function d(a){function b(a){a=\r\na||{};var b=!1,l;for(l in p)a[l]?b=!0:p[l]=0;b||(x=!1)}function g(a,b,t,f,g,d){var l,E=[],h=t.type;if(!k._callbacks[a])return[];\"keyup\"==h&&w(a)&&(b=[a]);for(l=0;l<k._callbacks[a].length;++l){var c=k._callbacks[a][l];if((f||!c.seq||p[c.seq]==c.level)&&h==c.action){var e;(e=\"keypress\"==h&&!t.metaKey&&!t.ctrlKey)||(e=c.modifiers,e=b.sort().join(\",\")===e.sort().join(\",\"));e&&(e=f&&c.seq==f&&c.level==d,(!f&&c.combo==g||e)&&k._callbacks[a].splice(l,1),E.push(c))}}return E}function c(a,b,c,f){k.stopCallback(b,\r\nb.target||b.srcElement,c,f)||!1!==a(b,c)||(b.preventDefault?b.preventDefault():b.returnValue=!1,b.stopPropagation?b.stopPropagation():b.cancelBubble=!0)}function e(a){\"number\"!==typeof a.which&&(a.which=a.keyCode);var b=z(a);b&&(\"keyup\"==a.type&&y===b?y=!1:k.handleKey(b,F(a),a))}function m(a,g,t,f){function h(c){return function(){x=c;++p[a];clearTimeout(q);q=setTimeout(b,1E3)}}function l(g){c(t,g,a);\"keyup\"!==f&&(y=z(g));setTimeout(b,10)}for(var d=p[a]=0;d<g.length;++d){var e=d+1===g.length?l:h(f||\r\nA(g[d+1]).action);n(g[d],e,f,a,d)}}function n(a,b,c,f,d){k._directMap[a+\":\"+c]=b;a=a.replace(/\\s+/g,\" \");var e=a.split(\" \");1<e.length?m(a,e,b,c):(c=A(a,c),k._callbacks[c.key]=k._callbacks[c.key]||[],g(c.key,c.modifiers,{type:c.action},f,a,d),k._callbacks[c.key][f?\"unshift\":\"push\"]({callback:b,modifiers:c.modifiers,action:c.action,seq:f,level:d,combo:a}))}var k=this;a=a||u;if(!(k instanceof d))return new d(a);k.target=a;k._callbacks={};k._directMap={};var p={},q,y=!1,r=!1,x=!1;k._handleKey=function(a,\r\nd,e){var f=g(a,d,e),h;d={};var k=0,l=!1;for(h=0;h<f.length;++h)f[h].seq&&(k=Math.max(k,f[h].level));for(h=0;h<f.length;++h)f[h].seq?f[h].level==k&&(l=!0,d[f[h].seq]=1,c(f[h].callback,e,f[h].combo,f[h].seq)):l||c(f[h].callback,e,f[h].combo);f=\"keypress\"==e.type&&r;e.type!=x||w(a)||f||b(d);r=l&&\"keydown\"==e.type};k._bindMultiple=function(a,b,c){for(var d=0;d<a.length;++d)n(a[d],b,c)};v(a,\"keypress\",e);v(a,\"keydown\",e);v(a,\"keyup\",e)}if(q){var n={8:\"backspace\",9:\"tab\",13:\"enter\",16:\"shift\",17:\"ctrl\",\r\n18:\"alt\",20:\"capslock\",27:\"esc\",32:\"space\",33:\"pageup\",34:\"pagedown\",35:\"end\",36:\"home\",37:\"left\",38:\"up\",39:\"right\",40:\"down\",45:\"ins\",46:\"del\",91:\"meta\",93:\"meta\",224:\"meta\"},r={106:\"*\",107:\"+\",109:\"-\",110:\".\",111:\"/\",186:\";\",187:\"=\",188:\",\",189:\"-\",190:\".\",191:\"/\",192:\"`\",219:\"[\",220:\"\\\\\",221:\"]\",222:\"'\"},C={\"~\":\"`\",\"!\":\"1\",\"@\":\"2\",\"#\":\"3\",$:\"4\",\"%\":\"5\",\"^\":\"6\",\"&\":\"7\",\"*\":\"8\",\"(\":\"9\",\")\":\"0\",_:\"-\",\"+\":\"=\",\":\":\";\",'\"':\"'\",\"<\":\",\",\">\":\".\",\"?\":\"/\",\"|\":\"\\\\\"},B={option:\"alt\",command:\"meta\",\"return\":\"enter\",\r\nescape:\"esc\",plus:\"+\",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?\"meta\":\"ctrl\"},p;for(c=1;20>c;++c)n[111+c]=\"f\"+c;for(c=0;9>=c;++c)n[c+96]=c.toString();d.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};d.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};d.prototype.trigger=function(a,b){if(this._directMap[a+\":\"+b])this._directMap[a+\":\"+b]({},a);return this};d.prototype.reset=function(){this._callbacks={};\r\nthis._directMap={};return this};d.prototype.stopCallback=function(a,b){if(-1<(\" \"+b.className+\" \").indexOf(\" mousetrap \")||D(b,this.target))return!1;if(\"composedPath\"in a&&\"function\"===typeof a.composedPath){var c=a.composedPath()[0];c!==a.target&&(b=c)}return\"INPUT\"==b.tagName||\"SELECT\"==b.tagName||\"TEXTAREA\"==b.tagName||b.isContentEditable};d.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};d.addKeycodes=function(a){for(var b in a)a.hasOwnProperty(b)&&(n[b]=a[b]);p=null};\r\nd.init=function(){var a=d(u),b;for(b in a)\"_\"!==b.charAt(0)&&(d[b]=function(b){return function(){return a[b].apply(a,arguments)}}(b))};d.init();\"undefined\"!==typeof module&&module.exports&&(module.exports=d);\"function\"===typeof define&&define.amd&&define(function(){return d})}})(\"undefined\"!==typeof window?window:null,\"undefined\"!==typeof window?document:null);\n"
},
"lib/observable": {
"content": "\n/*\nObservable\n==========\n\n`Observable` allows for observing arrays, functions, and objects.\n\nFunction dependencies are automagically observed.\n\nStandard array methods are proxied through to the underlying array.\n */\n\"use strict\";\nvar Observable, PROXY_LENGTH, copy, extend, last, magicDependency, noop, remove,\n __slice = [].slice;\n\nmodule.exports = Observable = function(value, context) {\n var changed, fn, listeners, notify, self;\n if (typeof (value != null ? value.observe : void 0) === \"function\") {\n return value;\n }\n listeners = [];\n notify = function(newValue) {\n self._value = newValue;\n return copy(listeners).forEach(function(listener) {\n return listener(newValue);\n });\n };\n if (typeof value === 'function') {\n fn = value;\n self = function() {\n magicDependency(self);\n return value;\n };\n self.releaseDependencies = function() {\n var _ref;\n return (_ref = self._observableDependencies) != null ? _ref.forEach(function(observable) {\n return observable.stopObserving(changed);\n }) : void 0;\n };\n changed = function() {\n var observableDependencies;\n observableDependencies = new Set;\n global.OBSERVABLE_ROOT_HACK.push(observableDependencies);\n try {\n value = fn.call(context);\n } finally {\n global.OBSERVABLE_ROOT_HACK.pop();\n }\n self.releaseDependencies();\n self._observableDependencies = observableDependencies;\n observableDependencies.forEach(function(observable) {\n return observable.observe(changed);\n });\n return notify(value);\n };\n changed();\n } else {\n self = function(newValue) {\n if (arguments.length > 0) {\n if (value !== newValue) {\n value = newValue;\n notify(newValue);\n }\n } else {\n magicDependency(self);\n }\n return value;\n };\n self.releaseDependencies = noop;\n self._value = value;\n }\n if (Array.isArray(value)) {\n [\"concat\", \"every\", \"filter\", \"forEach\", \"indexOf\", \"join\", \"lastIndexOf\", \"map\", \"reduce\", \"reduceRight\", \"slice\", \"some\"].forEach(function(method) {\n return self[method] = function() {\n var args;\n args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n magicDependency(self);\n return value[method].apply(value, args);\n };\n });\n [\"pop\", \"push\", \"reverse\", \"shift\", \"splice\", \"sort\", \"unshift\"].forEach(function(method) {\n return self[method] = function() {\n var args, returnValue;\n args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n returnValue = value[method].apply(value, args);\n notify(value);\n return returnValue;\n };\n });\n if (PROXY_LENGTH) {\n Object.defineProperty(self, 'length', {\n get: function() {\n magicDependency(self);\n return value.length;\n },\n set: function(length) {\n var returnValue;\n returnValue = value.length = length;\n notify(value);\n return returnValue;\n }\n });\n }\n extend(self, {\n remove: function(object) {\n var index, returnValue;\n index = value.indexOf(object);\n if (index >= 0) {\n returnValue = value.splice(index, 1)[0];\n notify(value);\n return returnValue;\n }\n },\n get: function(index) {\n magicDependency(self);\n return value[index];\n },\n first: function() {\n magicDependency(self);\n return value[0];\n },\n last: function() {\n magicDependency(self);\n return value[value.length - 1];\n },\n size: function() {\n magicDependency(self);\n return value.length;\n }\n });\n }\n extend(self, {\n listeners: listeners,\n observe: function(listener) {\n return listeners.push(listener);\n },\n stopObserving: function(fn) {\n return remove(listeners, fn);\n },\n toggle: function() {\n return self(!value);\n },\n increment: function(n) {\n if (n == null) {\n n = 1;\n }\n return self(Number(value) + n);\n },\n decrement: function(n) {\n if (n == null) {\n n = 1;\n }\n return self(Number(value) - n);\n },\n toString: function() {\n return \"Observable(\" + value + \")\";\n }\n });\n return self;\n};\n\nextend = Object.assign;\n\nglobal.OBSERVABLE_ROOT_HACK = [];\n\nmagicDependency = function(self) {\n var observerSet;\n observerSet = last(global.OBSERVABLE_ROOT_HACK);\n if (observerSet) {\n return observerSet.add(self);\n }\n};\n\nremove = function(array, value) {\n var index;\n index = array.indexOf(value);\n if (index >= 0) {\n return array.splice(index, 1)[0];\n }\n};\n\ncopy = function(array) {\n return array.concat([]);\n};\n\nlast = function(array) {\n return array[array.length - 1];\n};\n\nnoop = function() {};\n\ntry {\n Object.defineProperty((function() {}), 'length', {\n get: noop,\n set: noop\n });\n PROXY_LENGTH = true;\n} catch (_error) {\n PROXY_LENGTH = false;\n}\n"
},
"lib/polyfill": {
"content": "var endsWith, startsWith, _base, _base1;\n\nstartsWith = function(search, rawPos) {\n var pos;\n if (rawPos > 0) {\n pos = rawPos | 0;\n } else {\n pos = 0;\n }\n return this.substring(pos, pos + search.length) === search;\n};\n\nendsWith = function(search, l) {\n var length;\n length = this.length;\n if (l === void 0 || l > length) {\n l = length;\n }\n return this.substring(l - search.length, l) === search;\n};\n\nif ((_base = String.prototype).endsWith == null) {\n _base.endsWith = endsWith;\n}\n\nif ((_base1 = String.prototype).startsWith == null) {\n _base1.startsWith = startsWith;\n}\n\nmodule.exports = {\n startsWith: startsWith,\n endsWith: endsWith\n};\n"
},
"lib/postmaster": {
"content": "module.exports = require(\"postmaster\");\n"
},
"lib/runtime": {
"content": "var Bindable, Drop, Observable, Postmaster, Runtime, UI, applyStyle, deprecationWarning, version, _ref,\n __slice = [].slice;\n\napplyStyle = require(\"../util\").applyStyle;\n\nversion = require(\"../pixie\").version;\n\nPostmaster = require(\"./postmaster\");\n\n_ref = UI = require(\"../lib/ui\"), Bindable = _ref.Bindable, Observable = _ref.Observable;\n\nDrop = require(\"./drop\");\n\ndeprecationWarning = function(fn) {\n return function() {\n console.warn(\"DEPRECATED\");\n return fn.apply(this, arguments);\n };\n};\n\nRuntime = function(opts) {\n var BaseApp, applicationProxy, applicationTarget, client, eventListeners, externalObservables, heldApplicationMessages, host, hostTarget, initializeOnZineOS, lastEventListenerId, polyfillForStandalone, postmaster, readyPromise, remoteExists, system;\n if (opts == null) {\n opts = {};\n }\n if (opts.applyStyle) {\n applyStyle(UI.Style.all, 'system');\n }\n if (opts.logger == null) {\n opts.logger = {\n info: function() {},\n debug: function() {}\n };\n }\n externalObservables = {};\n heldApplicationMessages = [];\n postmaster = Postmaster({\n logger: opts.logger,\n delegate: {\n application: function() {\n var args, method, _ref1;\n method = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];\n if (applicationTarget.delegate) {\n return (_ref1 = applicationTarget.delegate)[method].apply(_ref1, args);\n } else {\n return new Promise(function(resolve, reject) {\n return heldApplicationMessages.push(function(delegate) {\n var e;\n try {\n return resolve(delegate[method].apply(delegate, args));\n } catch (_error) {\n e = _error;\n return reject(e);\n }\n });\n });\n }\n },\n updateSignal: function(name, newValue) {\n return externalObservables[name](newValue);\n },\n fn: function(handlerId, args) {\n return eventListeners[handlerId].apply(null, args);\n }\n }\n });\n remoteExists = postmaster.remoteTarget();\n applicationTarget = {\n observeSignal: function(name, handler) {\n var observable;\n observable = Observable();\n externalObservables[name] = observable;\n observable.observe(handler);\n return postmaster.invokeRemote(\"application\", \"observeSignal\", name).then(handler);\n }\n };\n applicationProxy = new Proxy(applicationTarget, {\n get: function(target, property, receiver) {\n return target[property] || function() {\n if (!remoteExists) {\n return;\n }\n return postmaster.invokeRemote.apply(postmaster, [\"application\", property].concat(__slice.call(arguments)));\n };\n },\n set: function(target, property, value, receiver) {\n if (property === \"delegate\") {\n heldApplicationMessages.forEach(function(fn) {\n return fn(value);\n });\n heldApplicationMessages = [];\n }\n target[property] = value;\n return target[property];\n }\n });\n lastEventListenerId = 0;\n eventListeners = {};\n readyPromise = null;\n hostTarget = {\n ready: function() {\n if (readyPromise) {\n return readyPromise;\n }\n if (remoteExists) {\n return readyPromise = postmaster.invokeRemote(\"ready\", {\n ZineOSClient: version,\n token: postmaster.token\n }).then(function(hostConfig) {\n var appData;\n appData = hostConfig != null ? hostConfig.ZineOS : void 0;\n if (appData) {\n initializeOnZineOS(appData);\n }\n return hostConfig;\n });\n } else {\n polyfillForStandalone();\n return readyPromise = Promise.resolve({\n standalone: true\n });\n }\n },\n on: function(eventName, handler) {\n lastEventListenerId += 1;\n eventListeners[lastEventListenerId] = handler;\n return postmaster.invokeRemote(\"system\", \"on\", eventName, lastEventListenerId);\n },\n off: function(eventName, handler) {\n var handlerId;\n handlerId = Object.keys(eventListeners).filter(function(id) {\n return eventListeners[id] === handler;\n })[0];\n delete eventListeners[handlerId];\n return postmaster.invokeRemote(\"system\", \"off\", eventName, handlerId);\n }\n };\n polyfillForStandalone = function() {\n return Object.assign(hostTarget, {\n readFile: function(path) {\n return fetch(path).then(function(response) {\n var _ref1;\n if ((200 <= (_ref1 = response.status) && _ref1 < 300)) {\n return response.blob();\n } else {\n throw new Error(response.statusText);\n }\n });\n },\n writeFile: function(path, blob) {\n return blob.download(path);\n }\n });\n };\n host = new Proxy(hostTarget, {\n get: function(target, property, receiver) {\n if (Object.prototype.hasOwnProperty.call(target, property)) {\n return target[property];\n } else {\n return function() {\n return postmaster.invokeRemote.apply(postmaster, [\"system\", property].concat(__slice.call(arguments)));\n };\n }\n }\n });\n initializeOnZineOS = function(_arg) {\n var id;\n id = _arg.id;\n applicationTarget.id = id;\n return document.addEventListener(\"mousedown\", function() {\n return applicationProxy.raiseToTop()[\"catch\"](console.warn);\n });\n };\n BaseApp = require(\"./app/index\")(host, applicationProxy);\n client = {\n postmaster: postmaster,\n util: {\n FileIO: deprecationWarning(BaseApp),\n applyStyle: applyStyle\n },\n Bindable: Bindable,\n Drop: Drop,\n Observable: UI.Observable,\n Jadelet2: UI.Jadelet2,\n Postmaster: Postmaster,\n UI: UI,\n version: version\n };\n system = {\n aws: require(\"./aws/index\"),\n client: client,\n config: {},\n fs: require(\"./fs/index\"),\n host: host,\n pkg: require(\"./pkg/index\"),\n ui: UI,\n util: {\n BaseApp: BaseApp,\n FileIO: deprecationWarning(BaseApp),\n Postmaster: Postmaster,\n applyStyle: applyStyle\n },\n version: version,\n readFile: function() {\n return host.readFile.apply(host, arguments);\n },\n readTree: function() {\n return host.readTree.apply(host, arguments);\n },\n writeFile: function() {\n return host.writeFile.apply(host, arguments);\n }\n };\n return {\n application: applicationProxy,\n system: system\n };\n};\n\nObject.assign(Runtime, {\n\n /*\n Launch the system client, attach `system` and `application` globals, send\n ready message, invoke callback.\n \n * Grab libraries through `system.client`.\n */\n launch: function(opts, fn) {\n if (typeof opts === 'function') {\n fn = opts;\n opts = {};\n }\n if (opts.debug) {\n opts.logger = console;\n }\n Object.assign(global, Runtime(opts));\n if ((typeof window !== \"undefined\" && window !== null) && Postmaster.dominant()) {\n window.addEventListener('unload', function() {\n return system.host.unload();\n });\n }\n return system.host.ready().then(function(hostConfig) {\n return Object.assign(system.config, hostConfig);\n })[\"finally\"](function() {\n return fn();\n });\n }\n});\n\nmodule.exports = Runtime;\n"
},
"lib/ui": {
"content": "var Action, ContextMenuView, Jadelet, MenuBarView, MenuItemView, MenuView, Modal, Observable, ProgressView, Style, TableView, WindowView;\n\nrequire(\"../setup\");\n\nAction = require(\"../action\");\n\nJadelet = require(\"./jadelet\");\n\nObservable = require(\"./observable\");\n\nStyle = require(\"../style\");\n\nContextMenuView = require(\"../views/context-menu\");\n\nModal = require(\"../modal\");\n\nMenuView = require(\"../views/menu\");\n\nMenuBarView = require(\"../views/menu-bar\");\n\nMenuItemView = require(\"../views/menu-item\");\n\nProgressView = require(\"../views/progress\");\n\nTableView = require(\"../views/table\");\n\nWindowView = require(\"../views/window\");\n\nmodule.exports = {\n AceEditor: require(\"../views/ace-editor\"),\n Action: Action,\n Bindable: require(\"./bindable\"),\n ContextMenu: ContextMenuView,\n Drop: require(\"./drop\"),\n Jadelet: Jadelet,\n Jadelet2: {\n compile: Jadelet.exec\n },\n Modal: Modal,\n Menu: MenuView,\n MenuBar: MenuBarView,\n MenuItem: MenuItemView,\n Observable: Observable,\n Progress: ProgressView,\n Style: {\n all: require(\"../style\")\n },\n Table: TableView,\n Util: {\n parseMenu: require(\"./indent-parse\")\n },\n Window: WindowView\n};\n"
},
"main": {
"content": "require(\"./lib/polyfill\");\n\nrequire(\"./lib/extensions\");\n\nif (PACKAGE.name === \"ROOT\") {\n require(\"./demo\");\n}\n\nmodule.exports = require(\"./lib/runtime\");\n"
},
"modal": {
"content": "\n/*\nModal\n\nDisplay modal alerts or dialogs.\n\nModal has promise returning equivalents of the native browser:\n\n- Alert\n- Confirm\n- Prompt\n\nThese accept the same arguments and return a promise fulfilled with\nthe same return value as the native methods.\n\nYou can display any element in the modal:\n\n modal.show myElement\n */\nvar CancelButtonTemplate, InputTemplate, Modal, ModalTemplate, PromptTemplate, cancellable, closeHandler, empty, formDataToObject, handle, modal, prompt, _ref;\n\n_ref = require(\"./util\"), formDataToObject = _ref.formDataToObject, handle = _ref.handle, empty = _ref.empty;\n\nPromptTemplate = require(\"./templates/modal/prompt\");\n\nModalTemplate = require(\"./templates/modal\");\n\nCancelButtonTemplate = require(\"./templates/cancel-button\");\n\nInputTemplate = require(\"./templates/input\");\n\nmodal = ModalTemplate();\n\ncancellable = true;\n\nmodal.onclick = function(e) {\n if (e.target === modal && cancellable) {\n return Modal.hide();\n }\n};\n\ndocument.addEventListener(\"keydown\", function(e) {\n if (!e.defaultPrevented) {\n if (e.key === \"Escape\" && cancellable) {\n e.preventDefault();\n return Modal.hide();\n }\n }\n});\n\ndocument.body.appendChild(modal);\n\ncloseHandler = null;\n\nprompt = function(params) {\n return new Promise(function(resolve) {\n var element, _ref1;\n element = PromptTemplate(params);\n Modal.show(element, {\n cancellable: false,\n closeHandler: resolve\n });\n return (_ref1 = element.querySelector(params.focus)) != null ? _ref1.focus() : void 0;\n });\n};\n\nmodule.exports = Modal = {\n show: function(element, options) {\n if (typeof options === \"function\") {\n closeHandler = options;\n } else {\n closeHandler = options != null ? options.closeHandler : void 0;\n if ((options != null ? options.cancellable : void 0) != null) {\n cancellable = options.cancellable;\n }\n }\n empty(modal).appendChild(element);\n return modal.classList.add(\"active\");\n },\n hide: function(dataForHandler) {\n if (typeof closeHandler === \"function\") {\n closeHandler(dataForHandler);\n }\n modal.classList.remove(\"active\");\n cancellable = true;\n return empty(modal);\n },\n alert: function(message) {\n return prompt({\n title: \"Alert\",\n message: message,\n focus: \"button\",\n confirm: handle(function() {\n return Modal.hide();\n })\n });\n },\n prompt: function(message, defaultValue, title) {\n if (defaultValue == null) {\n defaultValue = \"\";\n }\n if (title == null) {\n title = \"Prompt\";\n }\n return prompt({\n title: title,\n message: message,\n focus: \"input\",\n inputElement: InputTemplate({\n type: \"text\",\n value: defaultValue\n }),\n cancelButton: CancelButtonTemplate({\n cancel: handle(function() {\n return Modal.hide(null);\n })\n }),\n confirm: handle(function() {\n return Modal.hide(modal.querySelector(\"input\").value);\n })\n });\n },\n confirm: function(message, title) {\n if (title == null) {\n title = \"Confirm\";\n }\n return prompt({\n title: title,\n message: message,\n focus: \"button\",\n cancelButton: CancelButtonTemplate({\n cancel: handle(function() {\n return Modal.hide(false);\n })\n }),\n confirm: handle(function() {\n return Modal.hide(true);\n })\n });\n },\n form: function(formElement) {\n return new Promise(function(resolve) {\n var submitHandler;\n submitHandler = handle(function(e) {\n var formData, result;\n formData = new FormData(formElement);\n result = formDataToObject(formData);\n return Modal.hide(result);\n });\n formElement.addEventListener(\"submit\", submitHandler);\n return Modal.show(formElement, function(result) {\n formElement.removeEventListener(\"submit\", submitHandler);\n return resolve(result);\n });\n });\n }\n};\n"
},
"pixie": {
"content": "module.exports = {\n \"name\": \"system\",\n \"version\": \"0.5.0-pre.4\",\n \"publishPath\": \"/My Briefcase/public/danielx.net/\",\n \"dependencies\": {\n \"postmaster\": \"distri/postmaster:master\"\n },\n \"remoteDependencies\": [\n \"https://danielx.net/cdn/dexie/2.0.4.min.js\",\n \"https://danielx.whimsy.space/cdn/cognito/sdk.min.js\",\n \"https://danielx.whimsy.space/cdn/cognito/identity.min.js\",\n \"https://sdk.amazonaws.com/js/aws-sdk-2.7.20.min.js\"\n ],\n \"cognito\": {\n \"identityPoolId\": \"us-east-1:4fe22da5-bb5e-4a78-a260-74ae0a140bf9\",\n \"poolData\": {\n \"UserPoolId\": \"us-east-1_cfvrlBLXG\",\n \"ClientId\": \"3fd84r6idec9iork4e9l43mp61\"\n }\n }\n};"
},
"plugin/prometheus-preview": {
"content": "require(\"../setup\");\n"
},
"samples/notepad-menu": {
"content": "module.exports = \"[F]ile\\n [N]ew\\n [O]pen\\n [S]ave\\n Save [A]s\\n -\\n Page Set[u]p\\n [P]rint\\n -\\n E[x]it\\n[E]dit\\n [U]ndo\\n Redo\\n -\\n Cu[t]\\n [C]opy\\n [P]aste\\n De[l]ete\\n -\\n [F]ind\\n Find [N]ext\\n [R]eplace\\n [G]o To\\n -\\n Select [A]ll\\n Time/[D]ate\\nF[o]rmat\\n [W]ord Wrap\\n [F]ont...\\n[V]iew\\n [S]tatus Bar\\n[H]elp\\n View [H]elp\\n -\\n [A]bout Notepad\";\n"
},
"samples/test-form": {
"content": "module.exports = system.ui.Jadelet.exec([\"form\",{},[[\"h1\",{},[\"Cool Form Bro\"]],[\"p\",{},[[\"a\",{\"href\":\"https://yolo.biz\"},[\"Yolo\"]]]],[\"input\",{\"name\":\"yolo\"},[]],[\"input\",{\"name\":\"x\",\"value\":\"Lorem\"},[]],[\"input\",{\"name\":\"y\",\"value\":\"florem\"},[]],[\"input\",{\"name\":\"z\",\"type\":\"number\",\"value\":\"5\"},[]],[\"input\",{\"name\":\"file\",\"type\":\"file\"},[]],[\"textarea\",{\"name\":\"text\"},[]],[\"button\",{},[\"Submit\"]]]]);"
},
"setup": {
"content": "require(\"./lib/polyfill\");\n\nif (global.system == null) {\n global.system = {\n ui: {\n Jadelet: require(\"./lib/jadelet\")\n }\n };\n}\n"
},
"style": {
"content": "module.exports = \"* {\\n box-sizing: border-box;\\n}\\nbody,\\nhtml {\\n height: 100%;\\n}\\nbody {\\n display: flex;\\n flex-direction: column;\\n font-family: Sans-Serif;\\n font-size: 14px;\\n margin: 0;\\n}\\nbody > desktop {\\n z-index: 0;\\n}\\ninput,\\ntextarea,\\nkeygen,\\nselect,\\nbutton {\\n font-size: inherit;\\n font-family: inherit;\\n}\\nloader {\\n display: block;\\n padding: 1em;\\n}\\nloader > p:empty {\\n margin: 0;\\n}\\nloader > progress {\\n display: block;\\n}\\nmenu {\\n user-select: none;\\n -moz-user-select: none;\\n -webkit-user-select: none;\\n -ms-user-select: none;\\n background-color: #d3d3d3;\\n border-bottom: 1px solid rgba(0,0,0,0.5);\\n line-height: 18px;\\n margin: 0;\\n z-index: 1;\\n}\\nmenu:focus {\\n outline: none;\\n}\\nmenu.context {\\n z-index: 2000;\\n}\\nmenu-item {\\n display: block;\\n list-style-type: none;\\n}\\nmenu-item.active {\\n background-color: #000080;\\n color: #fff;\\n}\\nmenu-item > label {\\n display: flex;\\n padding: 0 0.25em;\\n white-space: nowrap;\\n}\\nmenu-item > label > * {\\n flex: 1 1 auto;\\n}\\nmenu-item > label > span.hotkey {\\n margin-left: 1em;\\n}\\nmenu-item > label > span.hotkey:empty {\\n margin-left: 0;\\n}\\nmenu-item > label > .decoration {\\n align-self: center;\\n flex: 0 1 auto;\\n line-height: 1em;\\n text-align: right;\\n margin-left: 0.5em;\\n padding-bottom: 0.125em;\\n}\\nmenu-item > label > .decoration:empty {\\n margin-left: 0;\\n}\\nmenu-item[disabled] {\\n color: #808080;\\n}\\nmenu-item[disabled].active {\\n background-color: rgba(0,0,0,0.125);\\n}\\nmenu.options {\\n border-top: 1px solid rgba(255,255,255,0.5);\\n border-left: 1px solid rgba(255,255,255,0.5);\\n border-right: 1px solid rgba(0,0,0,0.5);\\n box-shadow: 1px 2px 0px rgba(0,0,0,0.5);\\n display: none;\\n padding: 2px;\\n padding-bottom: 3px;\\n position: absolute;\\n}\\nmenu.options.active {\\n display: block;\\n}\\nmenu.options > menu-item.menu {\\n position: relative;\\n}\\nmenu.options > menu-item.menu > menu {\\n position: absolute;\\n left: 100%;\\n top: -3px;\\n margin-left: 1px;\\n}\\nmenu-item.menu.active > menu {\\n background-color: #d3d3d3;\\n color: #000;\\n display: block;\\n}\\nmenu.bar {\\n display: block;\\n flex: 0 0 auto;\\n margin: 0;\\n padding: 0;\\n position: initial;\\n white-space: nowrap;\\n overflow: hidden;\\n}\\nmenu.bar > menu-item {\\n display: inline-block;\\n}\\nmenu.bar > menu-item > label > .decoration {\\n display: none;\\n}\\nmenu.bar.accelerator-active span.accelerator {\\n text-decoration: underline;\\n}\\nmenu.options.bottoms-up menu-item.menu > menu {\\n top: initial;\\n bottom: -4px;\\n}\\n#modal {\\n align-items: center;\\n background-color: rgba(0,0,0,0.25);\\n display: none;\\n justify-content: center;\\n position: absolute;\\n z-index: 10000;\\n top: 0;\\n width: 100%;\\n height: 100%;\\n}\\n#modal.active {\\n display: flex;\\n}\\n#modal > * {\\n background-color: #fff;\\n border: 1px solid rgba(0,0,0,0.5);\\n box-shadow: 4px 4px 0 rgba(0,0,0,0.5);\\n max-width: 90%;\\n max-height: 90%;\\n}\\n#modal > form {\\n display: block;\\n padding: 1em;\\n}\\n#modal > form > h1 {\\n font-size: 1.5em;\\n margin: 0;\\n}\\n#modal > form > input,\\n#modal > form textarea {\\n display: block;\\n margin-bottom: 1em;\\n width: 100%;\\n}\\n#modal > form > button {\\n margin-right: 1em;\\n}\\n#modal > form > button:last-child {\\n margin-right: 0;\\n}\\ncontainer {\\n display: block;\\n height: 100%;\\n overflow: auto;\\n width: 100%;\\n}\\ntable {\\n border-collapse: collapse;\\n width: 100%;\\n}\\nth {\\n text-align: left;\\n}\\nthead {\\n border-bottom: 1px solid #000;\\n}\\ntd > input {\\n border: none;\\n background-color: transparent;\\n width: 100%;\\n height: 100%;\\n padding: 0;\\n}\\ntr:nth-child(even) {\\n background-color: #eee;\\n}\\ndesktop {\\n user-select: none;\\n -moz-user-select: none;\\n -webkit-user-select: none;\\n -ms-user-select: none;\\n display: block;\\n flex: 1 0 auto;\\n position: relative;\\n}\\nwindow {\\n user-select: none;\\n -moz-user-select: none;\\n -webkit-user-select: none;\\n -ms-user-select: none;\\n background-color: #d3d3d3;\\n border: 4px double rgba(0,0,0,0.87);\\n box-shadow: 1px 2px 0px rgba(0,0,0,0.5);\\n display: flex;\\n flex-direction: column;\\n position: absolute;\\n}\\nwindow > header {\\n background-color: #000080;\\n border-bottom: 1px solid rgba(0,0,0,0.87);\\n cursor: default;\\n display: flex;\\n flex: 0 0 auto;\\n line-height: 18px;\\n}\\nwindow > header > icon {\\n background-position: 50%;\\n background-repeat: no-repeat;\\n background-size: 16px;\\n color: #fff;\\n display: inline-block;\\n flex: 0 0 auto;\\n text-align: center;\\n width: 0;\\n}\\nwindow > header > control {\\n background-color: #d3d3d3;\\n color: #fff;\\n display: inline-block;\\n flex: 0 0 auto;\\n font-family: monospace;\\n width: 18px;\\n text-shadow: 1px 0px 0px #000, -1px 0px 0px #000, 0px -1px 0px #000, 0px 1px 0px #000, 1px 1px 0px #808080;\\n text-align: center;\\n border-left: 1px solid rgba(0,0,0,0.87);\\n}\\nwindow > header > control:first-child {\\n border-left: none;\\n}\\nwindow > header > control.close::before {\\n content: \\\"X\\\";\\n}\\nwindow > header > control.maximize::before {\\n content: \\\"⏫\\\";\\n}\\nwindow > header > control.minimize::before {\\n content: \\\"-\\\";\\n}\\nwindow > header > control.restore {\\n display: none;\\n}\\nwindow > header > control.restore::before {\\n content: \\\"⏬\\\";\\n}\\nwindow > header > title-bar {\\n color: #fff;\\n display: inline-block;\\n flex: 1 1 auto;\\n overflow: hidden;\\n padding: 0 1rem 0 6px;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n}\\nwindow > viewport {\\n background-color: #fff;\\n display: flex;\\n height: 100%;\\n overflow: auto;\\n position: relative;\\n z-index: 0;\\n}\\nwindow > viewport > * {\\n margin: auto;\\n}\\nwindow > viewport > textarea {\\n border: none;\\n font-family: monospace;\\n height: 100%;\\n padding: 2px 4px;\\n resize: none;\\n width: 100%;\\n}\\nwindow > viewport > iframe {\\n border: none;\\n height: 100%;\\n width: 100%;\\n position: absolute;\\n}\\nwindow > resize {\\n display: block;\\n position: absolute;\\n}\\nwindow > resize.e,\\nwindow > resize.w {\\n cursor: ew-resize;\\n}\\nwindow > resize.n,\\nwindow > resize.s {\\n cursor: ns-resize;\\n}\\nwindow > resize.h {\\n height: 4px;\\n width: 100%;\\n}\\nwindow > resize.v {\\n height: 100%;\\n width: 4px;\\n}\\nwindow > resize.w {\\n left: -4px;\\n}\\nwindow > resize.e {\\n right: -4px;\\n}\\nwindow > resize.n {\\n top: -4px;\\n}\\nwindow > resize.s {\\n bottom: -4px;\\n}\\nwindow > resize.n.w {\\n cursor: nw-resize;\\n}\\nwindow > resize.n.e {\\n cursor: ne-resize;\\n}\\nwindow > resize.s.e {\\n cursor: se-resize;\\n}\\nwindow > resize.s.w {\\n cursor: sw-resize;\\n}\\nwindow > resize.n.v {\\n border-bottom: 1px solid #000;\\n height: 23px;\\n}\\nwindow > resize.s.v {\\n border-top: 1px solid #000;\\n height: 23px;\\n}\\nwindow > resize.e.h {\\n border-left: 1px solid #000;\\n width: 22px;\\n}\\nwindow > resize.w.h {\\n border-right: 1px solid #000;\\n width: 22px;\\n}\\nwindow.minimized > header {\\n border-bottom: none;\\n}\\nwindow.minimized > header > control {\\n display: none;\\n}\\nwindow.minimized > header > control.minimize {\\n border-right: none;\\n display: inline-block;\\n}\\nwindow.minimized > header > control.minimize::before {\\n content: \\\"+\\\";\\n}\\nwindow.minimized > menu,\\nwindow.minimized > resize,\\nwindow.minimized > viewport {\\n display: none;\\n}\\nwindow.maximized {\\n border: none;\\n border-radius: 0;\\n height: 100%;\\n width: 100%;\\n}\\nwindow.maximized > resize {\\n display: none;\\n}\\nwindow.maximized > header {\\n position: absolute;\\n right: 0;\\n z-index: 2;\\n}\\nwindow.maximized > header > control {\\n display: none;\\n}\\nwindow.maximized > header > control.restore,\\nwindow.maximized > header > control.close {\\n display: inline-block;\\n}\\nframe-guard {\\n display: block;\\n height: 100%;\\n left: 0;\\n pointer-events: none;\\n position: fixed;\\n top: 0;\\n width: 100%;\\n z-index: 100000;\\n}\\nframe-guard.active {\\n pointer-events: auto;\\n}\\n\";"
},
"templates/cancel-button": {
"content": "module.exports = system.ui.Jadelet.exec([\"button\",{\"click\":{\"bind\":\"cancel\"}},[\"Cancel\"]]);"
},
"templates/input": {
"content": "module.exports = system.ui.Jadelet.exec([\"input\",{\"value\":{\"bind\":\"value\"},\"type\":{\"bind\":\"type\"}},[]]);"
},
"templates/menu-item": {
"content": "module.exports = system.ui.Jadelet.exec([\"menu-item\",{\"class\":[{\"bind\":\"class\"}],\"click\":{\"bind\":\"click\"},\"mousemove\":{\"bind\":\"mousemove\"},\"disabled\":{\"bind\":\"disabled\"}},[[\"label\",{},[{\"bind\":\"title\"},[\"span\",{\"class\":[\"hotkey\"]},[{\"bind\":\"hotkey\"}]],[\"span\",{\"class\":[\"decoration\"]},[{\"bind\":\"decoration\"}]]]],{\"bind\":\"content\"}]]);"
},
"templates/menu-separator": {
"content": "module.exports = system.ui.Jadelet.exec([\"menu-item\",{},[[\"hr\",{},[]]]]);"
},
"templates/menu": {
"content": "module.exports = system.ui.Jadelet.exec([\"menu\",{\"class\":[{\"bind\":\"class\"}],\"click\":{\"bind\":\"click\"},\"style\":[{\"bind\":\"style\"}]},[{\"bind\":\"items\"}]]);"
},
"templates/modal": {
"content": "module.exports = system.ui.Jadelet.exec([\"div\",{\"id\":[\"modal\"]},[]]);"
},
"templates/modal/prompt": {
"content": "module.exports = system.ui.Jadelet.exec([\"form\",{\"submit\":{\"bind\":\"confirm\"},\"tabindex\":\"-1\"},[[\"h1\",{},[{\"bind\":\"title\"}]],[\"p\",{},[{\"bind\":\"message\"}]],{\"bind\":\"inputElement\"},[\"button\",{},[\"OK\"]],{\"bind\":\"cancelButton\"}]]);"
},
"templates/progress": {
"content": "module.exports = system.ui.Jadelet.exec([\"loader\",{},[[\"p\",{},[{\"bind\":\"message\"}]],[\"progress\",{\"class\":[{\"bind\":\"class\"}],\"value\":{\"bind\":\"value\"},\"max\":{\"bind\":\"max\"}},[]]]]);"
},
"templates/reader-input": {
"content": "module.exports = function(options) {\n var input;\n if (options == null) {\n options = {};\n }\n input = document.createElement('input');\n input.type = \"file\";\n input.setAttribute(\"accept\", options.accept);\n input.onchange = function(e) {\n return typeof options.select === \"function\" ? options.select(input.files[0]) : void 0;\n };\n return input;\n};\n"
},
"templates/table": {
"content": "module.exports = system.ui.Jadelet.exec([\"container\",{},[[\"table\",{\"keydown\":{\"bind\":\"keydown\"}},[[\"thead\",{},[{\"bind\":\"headerElements\"}]],[\"tbody\",{},[]]]]]]);"
},
"templates/window": {
"content": "module.exports = system.ui.Jadelet.exec([\"window\",{\"class\":[{\"bind\":\"class\"}]},[[\"resize\",{\"class\":[\"n\",\"h\"]},[]],[\"resize\",{\"class\":[\"e\",\"v\"]},[]],[\"resize\",{\"class\":[\"s\",\"h\"]},[]],[\"resize\",{\"class\":[\"w\",\"v\"]},[]],[\"resize\",{\"class\":[\"n\",\"e\",\"h\"]},[]],[\"resize\",{\"class\":[\"n\",\"e\",\"v\"]},[]],[\"resize\",{\"class\":[\"n\",\"w\",\"h\"]},[]],[\"resize\",{\"class\":[\"n\",\"w\",\"v\"]},[]],[\"resize\",{\"class\":[\"s\",\"e\",\"h\"]},[]],[\"resize\",{\"class\":[\"s\",\"e\",\"v\"]},[]],[\"resize\",{\"class\":[\"s\",\"w\",\"h\"]},[]],[\"resize\",{\"class\":[\"s\",\"w\",\"v\"]},[]],[\"header\",{},[[\"icon\",{\"style\":[{\"bind\":\"iconStyle\"}]},[{\"bind\":\"iconEmoji\"}]],[\"title-bar\",{\"dblclick\":{\"bind\":\"maximize\"}},[{\"bind\":\"title\"}]],[\"control\",{\"class\":[\"minimize\"],\"click\":{\"bind\":\"minimize\"}},[]],[\"control\",{\"class\":[\"maximize\"],\"click\":{\"bind\":\"maximize\"}},[]],[\"control\",{\"class\":[\"restore\"],\"click\":{\"bind\":\"restore\"}},[]],[\"control\",{\"class\":[\"close\"],\"click\":{\"bind\":\"close\"}},[]]]],{\"bind\":\"menuBar\"},[\"viewport\",{},[{\"bind\":\"content\"}]]]]);"
},
"util": {
"content": "var A, F, Observable, S, Util, accelerateItem, advance, asElement, elementView, empty, entityMap, formDataToObject, get, handle, isDescendant, o,\n __slice = [].slice;\n\nObservable = require(\"./lib/observable\");\n\nA = function(attr) {\n return function(x) {\n return x[attr];\n };\n};\n\nF = function(methodName) {\n return function(x) {\n return x[methodName]();\n };\n};\n\nget = function(x, context) {\n if (typeof x === 'function') {\n return x.call(context);\n } else {\n return x;\n }\n};\n\no = function(object, name) {\n var attribute;\n attribute = Observable(object[name]);\n attribute.observe(function(newValue) {\n return object[name] = newValue;\n });\n return attribute;\n};\n\nhandle = function(fn) {\n return function(e) {\n if (e != null ? e.defaultPrevented : void 0) {\n return;\n }\n if (e != null) {\n e.preventDefault();\n }\n return fn.call(this, e);\n };\n};\n\nS = function(object, method, defaultValue) {\n return function() {\n if (typeof (object != null ? object[method] : void 0) === 'function') {\n return object[method]();\n } else {\n return defaultValue;\n }\n };\n};\n\nasElement = A('element');\n\naccelerateItem = function(items, key) {\n var acceleratedItem;\n acceleratedItem = items.filter(function(item) {\n return item.accelerator === key;\n })[0];\n if (acceleratedItem) {\n return acceleratedItem.click();\n }\n};\n\nisDescendant = function(element, ancestor) {\n var parent;\n if (!element) {\n return;\n }\n while ((parent = element.parentElement)) {\n if (element === ancestor) {\n return true;\n }\n element = parent;\n }\n};\n\nadvance = function(list, amount) {\n var activeItemIndex, currentItem;\n currentItem = list.filter(function(item) {\n return item.active();\n })[0];\n activeItemIndex = list.indexOf(currentItem) + amount;\n if (activeItemIndex < 0) {\n activeItemIndex = list.length - 1;\n } else if (activeItemIndex >= list.length) {\n activeItemIndex = 0;\n }\n return list[activeItemIndex];\n};\n\nformDataToObject = function(formData) {\n return Array.from(formData.entries()).reduce(function(object, _arg) {\n var key, value;\n key = _arg[0], value = _arg[1];\n object[key] = value;\n return object;\n }, {});\n};\n\nelementView = function(element) {\n if (!element) {\n return;\n }\n if (element.view) {\n return element.view;\n }\n return elementView(element.parentElement);\n};\n\nempty = function(node) {\n while (node.hasChildNodes()) {\n node.removeChild(node.lastChild);\n }\n return node;\n};\n\nmodule.exports = Util = {\n htmlEscape: function(string) {\n return String(string).replace(/[&<>\"'\\/]/g, function(s) {\n return entityMap[s];\n });\n },\n A: A,\n F: F,\n S: S,\n o: o,\n advance: advance,\n asElement: asElement,\n accelerateItem: accelerateItem,\n applyStyle: function(styleContent, className) {\n var style;\n if (className) {\n style = document.head.querySelector(\"style.\" + className) || document.createElement(\"style\");\n style.className = className;\n } else {\n style = document.createElement(\"style\");\n }\n style.innerHTML = styleContent;\n return document.head.appendChild(style);\n },\n elementView: elementView,\n empty: empty,\n formDataToObject: formDataToObject,\n get: get,\n handle: handle,\n isDescendant: isDescendant,\n exec: function(program, env, context) {\n var args, values;\n if (env == null) {\n env = {};\n }\n args = Object.keys(env);\n values = args.map(function(name) {\n return env[name];\n });\n return Function.apply(null, __slice.call(args).concat([program])).apply(context, values);\n },\n\n /*\n Require a single module in a package distribution that has\n no dependencies. A simple way to extract the module.exports from a\n style build for example.\n */\n crudeRequire: function(pkg, path) {\n var env, program;\n program = pkg.distribution[path].content;\n env = {\n module: {\n exports: {}\n }\n };\n Util.exec(program, env, env.module);\n return env.module.exports;\n }\n};\n\nentityMap = {\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': '&quot;',\n \"'\": '&#39;',\n \"/\": '&#x2F;'\n};\n"
},
"views/context-menu": {
"content": "\n/*\nContextMenu\n\nDisplay a context menu!\n\nQuestions:\n\nShould we be able to update the options in the menu after creation?\n */\nvar MenuView, Observable, isDescendant;\n\nObservable = require(\"../lib/observable\");\n\nMenuView = require(\"./menu\");\n\nisDescendant = require(\"../util\").isDescendant;\n\nmodule.exports = function(_arg) {\n var activeItem, classes, contextRoot, element, handlers, items, left, self, top;\n items = _arg.items, classes = _arg.classes, handlers = _arg.handlers;\n activeItem = Observable(null);\n if (classes == null) {\n classes = [];\n }\n top = Observable(\"\");\n left = Observable(\"\");\n contextRoot = {\n activeItem: activeItem,\n handlers: handlers\n };\n self = MenuView({\n items: items,\n contextRoot: contextRoot,\n classes: function() {\n return [\"context\", \"options\"].concat(classes);\n },\n style: function() {\n return \"top: \" + (top()) + \"px; left: \" + (left()) + \"px\";\n }\n });\n element = self.element;\n element.view = self;\n self.contextRoot = contextRoot;\n self.display = function(_arg1) {\n var inElement, x, y;\n inElement = _arg1.inElement, x = _arg1.x, y = _arg1.y;\n top(y);\n left(x);\n (inElement || document.body).appendChild(element);\n activeItem(self);\n return element.focus();\n };\n document.addEventListener(\"mousedown\", function(e) {\n if (!isDescendant(e.target, element)) {\n return activeItem(null);\n }\n });\n element.setAttribute(\"tabindex\", \"-1\");\n element.addEventListener(\"keydown\", function(e) {\n var currentItem, direction, key;\n key = e.key;\n switch (key) {\n case \"ArrowLeft\":\n case \"ArrowUp\":\n case \"ArrowRight\":\n case \"ArrowDown\":\n e.preventDefault();\n direction = key.replace(\"Arrow\", \"\");\n currentItem = activeItem();\n if (currentItem) {\n return currentItem.cursor(direction);\n }\n break;\n case \"Escape\":\n return activeItem(null);\n }\n });\n return self;\n};\n"
},
"views/menu-bar": {
"content": "var MenuView, Observable, advance, isDescendant, _ref;\n\nObservable = require(\"../lib/observable\");\n\nMenuView = require(\"./menu\");\n\n_ref = require(\"../util\"), isDescendant = _ref.isDescendant, advance = _ref.advance;\n\nmodule.exports = function(_arg) {\n var accelerateIfActive, acceleratorActive, activeItem, contextRoot, deactivate, element, handlers, items, previouslyFocusedElement, self;\n items = _arg.items, handlers = _arg.handlers;\n acceleratorActive = Observable(false);\n activeItem = Observable(null);\n previouslyFocusedElement = null;\n contextRoot = {\n activeItem: activeItem,\n handlers: handlers\n };\n self = MenuView({\n classes: function() {\n return [\"bar\", acceleratorActive() ? \"accelerator-active\" : void 0];\n },\n items: items,\n contextRoot: contextRoot\n });\n element = self.element;\n self.cursor = function(direction) {\n switch (direction) {\n case \"Right\":\n return self.advance(1);\n case \"Left\":\n return self.advance(-1);\n }\n };\n self.items.forEach(function(item) {\n item.horizontal = true;\n return item.cursor = function(direction) {\n var _ref1, _ref2;\n console.log(\"Item\", direction);\n if (direction === \"Down\") {\n return (_ref1 = item.submenu) != null ? _ref1.advance(1) : void 0;\n } else if (direction === \"Up\") {\n return (_ref2 = item.submenu) != null ? _ref2.advance(-1) : void 0;\n } else {\n return item.parent.cursor(direction);\n }\n };\n });\n deactivate = function() {\n activeItem(null);\n acceleratorActive(false);\n return previouslyFocusedElement != null ? previouslyFocusedElement.focus() : void 0;\n };\n document.addEventListener(\"mousedown\", function(e) {\n if (!isDescendant(e.target, element)) {\n acceleratorActive(false);\n return activeItem(null);\n }\n });\n document.addEventListener(\"keydown\", function(e) {\n var key, menuIsActive, _ref1;\n key = e.key;\n switch (key) {\n case \"Enter\":\n return (_ref1 = activeItem()) != null ? _ref1.click() : void 0;\n case \"Alt\":\n menuIsActive = false;\n if (acceleratorActive() || menuIsActive) {\n return deactivate();\n } else {\n previouslyFocusedElement = document.activeElement;\n element.focus();\n if (!activeItem()) {\n activeItem(self);\n }\n return acceleratorActive(true);\n }\n }\n });\n accelerateIfActive = function(key) {\n var _ref1;\n if (acceleratorActive()) {\n return (_ref1 = activeItem()) != null ? _ref1.accelerate(key) : void 0;\n }\n };\n element.setAttribute(\"tabindex\", \"-1\");\n element.addEventListener(\"keydown\", function(e) {\n var accelerated, currentItem, direction, key;\n key = e.key;\n switch (key) {\n case \"ArrowLeft\":\n case \"ArrowUp\":\n case \"ArrowRight\":\n case \"ArrowDown\":\n e.preventDefault();\n direction = key.replace(\"Arrow\", \"\");\n currentItem = activeItem();\n if (currentItem) {\n return currentItem.cursor(direction);\n }\n break;\n case \"Escape\":\n return deactivate();\n default:\n accelerated = accelerateIfActive(key.toLowerCase());\n if (accelerated != null) {\n return e.preventDefault();\n }\n }\n });\n return self;\n};\n"
},
"views/menu-item": {
"content": "var F, MenuItemTemplate, S, accelerateItem, advance, asElement, formatAction, formatLabel, handle, htmlEscape, isDescendant, _ref;\n\n_ref = require(\"../util\"), advance = _ref.advance, htmlEscape = _ref.htmlEscape, asElement = _ref.asElement, F = _ref.F, S = _ref.S, isDescendant = _ref.isDescendant, accelerateItem = _ref.accelerateItem, handle = _ref.handle;\n\nMenuItemTemplate = require(\"../templates/menu-item\");\n\nmodule.exports = function(_arg) {\n var MenuView, accelerator, action, actionName, active, activeItem, click, content, contextRoot, disabled, element, handlers, hotkey, items, label, labelText, parent, self, submenu, title, _ref1, _ref2;\n label = _arg.label, MenuView = _arg.MenuView, items = _arg.items, contextRoot = _arg.contextRoot, parent = _arg.parent;\n self = {};\n activeItem = contextRoot.activeItem, handlers = contextRoot.handlers;\n active = function() {\n var _ref1;\n return isDescendant((_ref1 = activeItem()) != null ? _ref1.element : void 0, element);\n };\n self.active = active;\n if (items) {\n submenu = MenuView({\n items: items,\n contextRoot: contextRoot,\n parent: self\n });\n content = submenu.element;\n }\n _ref1 = formatAction(label), labelText = _ref1[0], actionName = _ref1[1];\n _ref2 = formatLabel(labelText), title = _ref2[0], accelerator = _ref2[1];\n action = handlers[actionName];\n disabled = S(action, \"disabled\", false);\n hotkey = S(action, \"hotkey\", \"\");\n click = function(e) {\n if (disabled()) {\n return;\n }\n if (e != null ? e.defaultPrevented : void 0) {\n return;\n }\n if (e != null) {\n e.preventDefault();\n }\n if (submenu) {\n activeItem(submenu);\n return;\n }\n if (action != null) {\n if (typeof action.call === \"function\") {\n action.call(handlers);\n }\n }\n return activeItem(null);\n };\n element = MenuItemTemplate({\n \"class\": function() {\n return [items ? \"menu\" : void 0, active() ? \"active\" : void 0];\n },\n click: click,\n mousemove: function(e) {\n if (!activeItem()) {\n return;\n }\n if (!e.defaultPrevented && isDescendant(e.target, element)) {\n e.preventDefault();\n return activeItem(self);\n }\n },\n title: title,\n content: content,\n decoration: items ? \"▸\" : void 0,\n hotkey: hotkey,\n disabled: disabled\n });\n Object.assign(self, {\n accelerator: accelerator,\n accelerate: function(key) {\n if (submenu) {\n return submenu.accelerate(key);\n } else {\n return parent.accelerate(key);\n }\n },\n click: click,\n parent: parent,\n element: element,\n submenu: submenu,\n cursor: function(direction) {\n console.log(\"Item Cursor\", direction);\n if (submenu && direction === \"Right\") {\n return activeItem(submenu.navigableItems[0]);\n } else if (parent.parent && direction === \"Left\") {\n if (parent.parent.horizontal) {\n return parent.parent.cursor(direction);\n } else {\n return activeItem(parent.parent);\n }\n } else {\n return parent.cursor(direction);\n }\n }\n });\n return self;\n};\n\nformatAction = function(labelText) {\n var action, title, _ref1;\n _ref1 = labelText.split(\"->\").map(F(\"trim\")), title = _ref1[0], action = _ref1[1];\n if (!action) {\n action = title.replace(/[^A-Za-z0-9]/g, \"\");\n action = action.charAt(0).toLowerCase() + action.substring(1);\n }\n return [title, action];\n};\n\nformatLabel = function(labelText) {\n var accelerator, span, titleHTML;\n accelerator = void 0;\n titleHTML = htmlEscape(labelText).replace(/\\[([^\\]]+)\\]/, function(match, $1) {\n accelerator = $1.toLowerCase();\n return \"<span class=\\\"accelerator\\\">\" + $1 + \"</span>\";\n });\n span = document.createElement(\"span\");\n span.innerHTML = titleHTML;\n return [span, accelerator];\n};\n"
},
"views/menu-separator": {
"content": "var MenuSeparatorTemplate;\n\nMenuSeparatorTemplate = require(\"../templates/menu-separator\");\n\nmodule.exports = function() {\n return {\n element: MenuSeparatorTemplate(),\n separator: true\n };\n};\n"
},
"views/menu": {
"content": "var F, MenuItemTemplate, MenuItemView, MenuTemplate, MenuView, Observable, S, SeparatorView, accelerateItem, advance, asElement, assert, get, handle, htmlEscape, isDescendant, parseMenu, _ref;\n\nObservable = require(\"../lib/observable\");\n\nassert = require(\"../lib/assert\");\n\n_ref = require(\"../util\"), advance = _ref.advance, accelerateItem = _ref.accelerateItem, asElement = _ref.asElement, get = _ref.get, F = _ref.F, S = _ref.S, htmlEscape = _ref.htmlEscape, handle = _ref.handle, isDescendant = _ref.isDescendant;\n\nMenuTemplate = require(\"../templates/menu\");\n\nMenuItemTemplate = require(\"../templates/menu-item\");\n\nSeparatorView = require(\"./menu-separator\");\n\nMenuItemView = require(\"./menu-item\");\n\nparseMenu = require(\"../lib/indent-parse\");\n\nmodule.exports = MenuView = function(_arg) {\n var active, activeItem, classes, contextRoot, getItems, items, navigableItems, parent, self, style;\n items = _arg.items, classes = _arg.classes, style = _arg.style, contextRoot = _arg.contextRoot, parent = _arg.parent;\n self = {};\n if (contextRoot == null) {\n contextRoot = {\n activeItem: Observable(),\n handlers: {}\n };\n }\n if (classes == null) {\n classes = function() {\n return [\"options\"];\n };\n }\n activeItem = contextRoot.activeItem;\n if (typeof items === \"string\") {\n items = parseMenu(items);\n }\n getItems = Observable(function() {\n return items.map(function(item) {\n var label, submenuItems;\n switch (false) {\n case !(typeof item === \"string\" && item.match(/^[=-]+$/)):\n return SeparatorView();\n case !Array.isArray(item):\n assert(item.length === 2);\n label = item[0], submenuItems = item[1];\n return MenuItemView({\n label: label,\n items: submenuItems,\n MenuView: MenuView,\n contextRoot: contextRoot,\n parent: self\n });\n default:\n return MenuItemView({\n label: item,\n contextRoot: contextRoot,\n parent: self\n });\n }\n });\n });\n navigableItems = Observable(function() {\n return getItems().filter(function(item) {\n return !item.separator;\n });\n });\n active = function() {\n var _ref1;\n return isDescendant((_ref1 = activeItem()) != null ? _ref1.element : void 0, self.element);\n };\n Object.assign(self, {\n accelerate: function(key) {\n return accelerateItem(getItems(), key);\n },\n cursor: function(direction) {\n var _ref1;\n switch (direction) {\n case \"Up\":\n return self.advance(-1);\n case \"Down\":\n return self.advance(1);\n default:\n return (_ref1 = parent.parent) != null ? _ref1.cursor(direction) : void 0;\n }\n },\n parent: parent,\n items: getItems,\n advance: function(n) {\n return activeItem(advance(navigableItems(), n));\n },\n navigableItems: navigableItems,\n element: MenuTemplate({\n style: style,\n \"class\": function() {\n return [active() ? \"active\" : void 0].concat(classes());\n },\n click: handle(function(e) {\n return activeItem(self);\n }),\n items: function() {\n return getItems().map(asElement);\n }\n })\n });\n return self;\n};\n"
},
"views/progress": {
"content": "var Observable, Template;\n\nTemplate = require(\"../templates/progress\");\n\nObservable = require(\"../lib/observable\");\n\n\"\";\n\nmodule.exports = function(params) {\n var element, max, message, value;\n if (params == null) {\n params = {};\n }\n value = params.value, max = params.max, message = params.message;\n value = Observable(value || 0);\n max = Observable(max);\n message = Observable(message);\n element = Template({\n value: value,\n max: max,\n message: message\n });\n return {\n element: element,\n value: value,\n message: message,\n max: max\n };\n};\n"
},
"views/table": {
"content": "var TableTemplate, TableView, advanceRow, empty;\n\nempty = require(\"../util\").empty;\n\nTableTemplate = require(\"../templates/table\");\n\nadvanceRow = function(path, prev) {\n var cellIndex, input, nextRowElement, td, tr;\n td = path.filter(function(element) {\n return element.tagName === \"TD\";\n })[0];\n if (!td) {\n return;\n }\n tr = td.parentElement;\n cellIndex = Array.prototype.indexOf.call(tr.children, td);\n if (prev) {\n nextRowElement = tr.previousSibling;\n } else {\n nextRowElement = tr.nextSibling;\n }\n if (nextRowElement) {\n input = nextRowElement.children[cellIndex].querySelector('input');\n return input != null ? input.focus() : void 0;\n }\n};\n\nTableView = function(_arg) {\n var RowElement, containerElement, data, filterAndSort, filterFn, headers, rowElements, tableBody, update;\n data = _arg.data, headers = _arg.headers, RowElement = _arg.RowElement;\n if (headers == null) {\n headers = Object.keys(data[0]);\n }\n containerElement = TableTemplate({\n headerElements: headers.map(function(header) {\n var th;\n th = document.createElement('th');\n th.innerText = header;\n return th;\n }),\n keydown: function(event) {\n var key, path;\n key = event.key, path = event.path;\n switch (key) {\n case \"Enter\":\n case \"ArrowDown\":\n event.preventDefault();\n return advanceRow(path);\n case \"ArrowUp\":\n event.preventDefault();\n return advanceRow(path, true);\n }\n }\n });\n tableBody = containerElement.querySelector('tbody');\n filterFn = function(datum) {\n return true;\n };\n filterAndSort = function(data, filterFn, sortFn) {\n var filteredData;\n if (filterFn == null) {\n filterFn = function() {\n return true;\n };\n }\n filteredData = data.filter(filterFn);\n if (sortFn) {\n return filteredData.sort(sortFn);\n } else {\n return filteredData;\n }\n };\n rowElements = function() {\n return filterAndSort(data, filterFn, null).map(RowElement);\n };\n update = function() {\n empty(tableBody);\n return rowElements().forEach(function(element) {\n return tableBody.appendChild(element);\n });\n };\n update();\n return {\n element: containerElement,\n render: update\n };\n};\n\nmodule.exports = TableView;\n"
},
"views/window": {
"content": "var Bindable, Observable, WindowTemplate, activeDrag, activeResize, dragStart, elementView, frameGuard, raiseToTop, resizeInitial, resizeStart, styleBind, topIndex;\n\nWindowTemplate = require(\"../templates/window\");\n\nelementView = require(\"../util\").elementView;\n\nframeGuard = document.createElement(\"frame-guard\");\n\ndocument.body.appendChild(frameGuard);\n\ntopIndex = 0;\n\nraiseToTop = function(view) {\n var zIndex;\n if (typeof view.zIndex !== 'function') {\n return;\n }\n zIndex = view.zIndex();\n if (zIndex === topIndex) {\n return;\n }\n topIndex += 1;\n return view.zIndex(topIndex);\n};\n\nactiveDrag = null;\n\ndragStart = null;\n\ndocument.addEventListener(\"mousedown\", function(e) {\n var target, view;\n target = e.target;\n view = elementView(target);\n if (view) {\n raiseToTop(view);\n }\n if (target.tagName === \"TITLE-BAR\") {\n dragStart = e;\n return activeDrag = view;\n }\n});\n\ndocument.addEventListener(\"mousemove\", function(e) {\n var bottomEdge, dx, dy, hotHeight, hotWidth, hotX, hotY, leftEdge, prevX, prevY, rightEdge, topEdge, x, xPos, y, yPos;\n if (activeDrag) {\n frameGuard.classList.add(\"active\");\n prevX = dragStart.clientX, prevY = dragStart.clientY;\n x = e.clientX, y = e.clientY;\n dragStart = e;\n if (activeDrag.maximized()) {\n activeDrag.restore();\n activeDrag.x(x - activeDrag.width() / 2);\n activeDrag.y(y - 30);\n }\n dx = x - prevX;\n dy = y - prevY;\n activeDrag.x(activeDrag.x() + dx);\n activeDrag.y(activeDrag.y() + dy);\n leftEdge = x <= 10;\n rightEdge = x >= document.body.getBoundingClientRect().width - 10;\n hotX = leftEdge || rightEdge;\n topEdge = y <= 10;\n bottomEdge = y >= document.body.getBoundingClientRect().height - 10;\n hotY = topEdge || bottomEdge;\n if (hotX || hotY) {\n xPos = \"0%\";\n yPos = \"0%\";\n hotWidth = \"50%\";\n hotHeight = \"50%\";\n if (!hotX) {\n hotWidth = \"100%\";\n }\n if (!hotY) {\n hotHeight = \"100%\";\n }\n if (bottomEdge) {\n yPos = \"50%\";\n }\n if (rightEdge) {\n xPos = \"50%\";\n }\n activeDrag.saveSize();\n activeDrag.maximized(true);\n activeDrag.x(xPos);\n activeDrag.y(yPos);\n activeDrag.width(hotWidth);\n return activeDrag.height(hotHeight);\n }\n }\n});\n\nactiveResize = null;\n\nresizeStart = null;\n\nresizeInitial = null;\n\ndocument.addEventListener(\"mousedown\", function(e) {\n var height, target, width, x, y, _ref;\n target = e.target;\n if (target.tagName === \"RESIZE\") {\n frameGuard.classList.add(\"active\");\n resizeStart = e;\n activeResize = target;\n _ref = elementView(activeResize), width = _ref.width, height = _ref.height, x = _ref.x, y = _ref.y;\n return resizeInitial = {\n width: width(),\n height: height(),\n x: x(),\n y: y()\n };\n }\n});\n\ndocument.addEventListener(\"mousemove\", function(e) {\n var actualDeltaX, actualDeltaY, dx, dy, height, startX, startY, view, width, x, y;\n if (activeResize) {\n startX = resizeStart.clientX, startY = resizeStart.clientY;\n x = e.clientX, y = e.clientY;\n dx = x - startX;\n dy = y - startY;\n width = resizeInitial.width;\n height = resizeInitial.height;\n if (activeResize.classList.contains(\"e\")) {\n width += dx;\n }\n if (activeResize.classList.contains(\"w\")) {\n width -= dx;\n }\n if (activeResize.classList.contains(\"s\")) {\n height += dy;\n }\n if (activeResize.classList.contains(\"n\")) {\n height -= dy;\n }\n width = Math.max(width, 200);\n height = Math.max(height, 50);\n actualDeltaX = width - resizeInitial.width;\n actualDeltaY = height - resizeInitial.height;\n view = elementView(activeResize);\n if (activeResize.classList.contains(\"n\")) {\n view.y(resizeInitial.y - actualDeltaY);\n }\n if (activeResize.classList.contains(\"w\")) {\n view.x(resizeInitial.x - actualDeltaX);\n }\n view.width(width);\n view.height(height);\n return view.trigger(\"resize\");\n }\n});\n\ndocument.addEventListener(\"mouseup\", function() {\n activeDrag = null;\n activeResize = null;\n return frameGuard.classList.remove(\"active\");\n});\n\nBindable = require(\"../lib/bindable\");\n\nObservable = require(\"../lib/observable\");\n\nmodule.exports = function(params) {\n var element, height, iconEmoji, iconStyle, iconURL, maximized, minimized, prevHeight, prevWidth, prevX, prevY, restore, self, title, width, x, y, zIndex, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;\n self = Bindable();\n x = Observable((_ref = params.x) != null ? _ref : 50);\n y = Observable((_ref1 = params.y) != null ? _ref1 : 50);\n width = Observable((_ref2 = params.width) != null ? _ref2 : 400);\n height = Observable((_ref3 = params.height) != null ? _ref3 : 300);\n title = Observable((_ref4 = params.title) != null ? _ref4 : \"Untitled\");\n minimized = Observable(false);\n maximized = Observable(false);\n prevWidth = Observable(null);\n prevHeight = Observable(null);\n prevX = Observable(null);\n prevY = Observable(null);\n iconURL = Observable(params.iconURL || \"data:image/x-icon;base64,AAABAAIAICAQAAEABADoAgAAJgAAABAQEAABAAQAKAEAAA4DAAAoAAAAIAAAAEAAAAABAAQAAAAAAIACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvwAAvwAAAL+/AL8AAAC/AL8Av78AAMDAwACAgIAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAACHd3d3d3d3d3d3d3dwAAAAj///////////////cAAAAI///////////////3AAAACP///////3d///d39wAAAAj/zMzM//mZ//+Zn/cAAAAI////////l///+X/3AAAACP/MzMzM//l3d3l/9wAAAAj/////////mZmZf/cAAAAI/8zMzMzM//l/+X/3AAAACP//////////l/l/9wAAAAj/zMzMzMzM//l5f/cAAAAI////////////mX/3AAAACP/MzMzMzMzM//n/9wAAAAj///////////////cAAAAI/8zMzMzMzMzMzP/3AAAACP//////////////9wAAAAj/zMzMzMzMzMzM//cAAAAI///////////////3AAAACP8AAAAA/8zMzMz/9wAAAAj/iZD/8P////////cAAAAI/4AAAAD/zMzMzP/3AAAACP+P8Luw////////9wAAAAj/gAC7sP/MzMzM//cAAAAI/4/wu7D////////3AAAACP+P8Luw/////4AAAAAAAAj/j/AAAP////+P94AAAAAI/4/wzMD/////j3gAAAAACP+IiIiA/////4eAAAAAAAj///////////+IAAAAAAAI////////////gAAAAAAACIiIiIiIiIiIiIAAAAAA4AAAB+AAAAfgAAAH4AAAB+AAAAfgAAAH4AAAB+AAAAfgAAAH4AAAB+AAAAfgAAAH4AAAB+AAAAfgAAAH4AAAB+AAAAfgAAAH4AAAB+AAAAfgAAAH4AAAB+AAAAfgAAAH4AAAB+AAAAfgAAAP4AAAH+AAAD/gAAB/4AAA/+AAAf8oAAAAEAAAACAAAAABAAQAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvwAAvwAAAL+/AL8AAAC/AL8Av78AAMDAwACAgIAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAACHd3d3d3cAAI//////9wAAj8z5//n3AACP//+ZmfcAAI/MzPn59wAAj////5n3AACPzMzM+fcAAI//////9wAAjwAPzMz3AACPmY////cAAI/Pj8zM9wAAj8+P//AAAACPiI//9/gAAI/////3gAAAiIiIiIgAAAgAMAAIADAACAAwAAgAMAAIADAACAAwAAgAMAAIADAACAAwAAgAMAAIADAACAAwAAgAMAAIAHAACADwAAgB8AAA==\");\n iconEmoji = Observable(params.iconEmoji || null);\n iconStyle = Observable(function() {\n if (iconEmoji()) {\n return \"width: 18px;\";\n } else {\n return \"background-image: url(\" + (iconURL()) + \");\\nwidth: 18px;\";\n }\n });\n topIndex += 1;\n zIndex = Observable((_ref5 = params.zIndex) != null ? _ref5 : topIndex);\n element = WindowTemplate({\n iconStyle: iconStyle,\n iconEmoji: iconEmoji,\n title: title,\n menuBar: params.menuBar,\n content: params.content,\n \"class\": function() {\n return [minimized() ? \"minimized\" : void 0, maximized() ? \"maximized\" : void 0];\n },\n close: function() {\n return self.close();\n },\n minimize: function() {\n return self.minimize();\n },\n maximize: function() {\n return self.maximize();\n },\n restore: function() {\n return self.restore();\n }\n });\n styleBind(y, element, \"top\", \"px\");\n styleBind(x, element, \"left\", \"px\");\n styleBind(height, element, \"height\", \"px\");\n styleBind(width, element, \"width\", \"px\");\n styleBind(zIndex, element, \"zIndex\");\n restore = function() {\n if (prevX() != null) {\n x(prevX());\n }\n if (prevY() != null) {\n y(prevY());\n }\n width(prevWidth());\n height(prevHeight());\n minimized(false);\n maximized(false);\n return self.trigger(\"resize\");\n };\n Object.assign(self, {\n element: element,\n iconEmoji: iconEmoji,\n iconURL: iconURL,\n title: title,\n x: x,\n y: y,\n width: width,\n height: height,\n zIndex: zIndex,\n close: function() {\n return element.remove();\n },\n saveSize: function() {\n prevWidth(width());\n prevHeight(height());\n if (typeof x() === 'number') {\n prevX(x());\n }\n if (typeof y() === 'number') {\n return prevY(y());\n }\n },\n maximized: maximized,\n maximize: function() {\n maximized.toggle();\n if (maximized()) {\n self.saveSize();\n width(null);\n height(null);\n x(0);\n y(0);\n self.trigger(\"resize\");\n return self.trigger(\"maximize\");\n } else {\n return restore();\n }\n },\n minimized: minimized,\n minimize: function() {\n minimized.toggle();\n if (minimized()) {\n prevWidth(width());\n prevHeight(height());\n width(null);\n height(null);\n self.trigger(\"resize\");\n return self.trigger(\"minimize\");\n } else {\n return restore();\n }\n },\n restore: function() {\n return restore();\n },\n raiseToTop: function() {\n return raiseToTop(self);\n }\n });\n element.view = self;\n return self;\n};\n\nstyleBind = function(observable, element, attr, suffix) {\n var update;\n if (suffix == null) {\n suffix = \"\";\n }\n update = function(newValue) {\n if (typeof newValue === \"string\") {\n return element.style[attr] = newValue;\n } else if ((newValue != null) && ((newValue = parseInt(newValue)) != null)) {\n return element.style[attr] = \"\" + newValue + suffix;\n } else {\n return element.style[attr] = null;\n }\n };\n observable.observe(update);\n return update(observable());\n};\n"
},
"workspaces/size": {
"content": "var fmt, items, pre, total;\n\nfmt = function(size) {\n return size.toString().padStart(10);\n};\n\ntotal = 0;\n\nitems = Object.keys(PACKAGE.source).map(function(name) {\n return [name, PACKAGE.source[name].content.length];\n}).sort(function(a, b) {\n return b[1] - a[1];\n}).map(function(_arg) {\n var name, size;\n name = _arg[0], size = _arg[1];\n total += size;\n return fmt(size) + \" \" + name;\n}).join(\"\\n\");\n\npre = document.createElement(\"pre\");\n\npre.innerText = items + \"\\n------------------------------\\n\" + fmt(total) + \" Total\";\n\npre.style.overflow = \"auto\";\n\ndocument.body.appendChild(pre);\n"
},
"lib/aws/index": {
"content": "\n/*\nInterface to all our AWS madness.\n\nWould a better name be 'server' or 'network'?\n */\nvar urlSafeSHA256;\n\nurlSafeSHA256 = require(\"../util/index\").urlSafeSHA256;\n\nmodule.exports = {\n Cognito: require(\"./cognito\"),\n api: function(path, params) {\n var url;\n if (params == null) {\n params = {};\n }\n url = new URL(\"https://api.whimsy.space/\" + path);\n url.searchParams.append(\"idpjwt\", Object.values(AWS.config.credentials.params.Logins)[0]);\n if (params.body != null) {\n params.body = JSON.stringify(params.body);\n }\n return fetch(url, params);\n },\n cdn: function(blob) {\n var S3, id, queryExisting;\n S3 = new AWS.S3({\n params: {\n Bucket: \"whimsy-fs\"\n }\n });\n S3.config.credentials = AWS.config.credentials;\n id = AWS.config.credentials.identityId;\n queryExisting = function(sha) {\n return fetch(\"https://whimsy.space/cdn/\" + sha, {\n method: 'HEAD'\n }).then(function(response) {\n return response.status === 200;\n });\n };\n return urlSafeSHA256(blob).then(function(sha) {\n return queryExisting(sha).then(function(found) {\n if (found) {\n return sha;\n }\n return S3.putObject({\n Key: \"incoming/\" + id + \"/\" + sha,\n ContentType: blob.type,\n Body: blob\n }).promise().then(function() {\n return new Promise(function(resolve, reject) {\n var check, n, timeout;\n timeout = 1000;\n n = 0;\n check = function() {\n n += 1;\n if (n <= 10) {\n return queryExisting(sha).then(function(found) {\n if (found) {\n return resolve(sha);\n } else {\n return setTimeout(function() {\n return check();\n }, timeout);\n }\n });\n } else {\n return reject();\n }\n };\n return check();\n });\n });\n });\n });\n }\n};\n"
},
"lib/util/index": {
"content": "var base64URLEncode, bufferToBase64, digest;\n\nmodule.exports = {\n urlSafeSHA256: function(blob) {\n return blob.readAsArrayBuffer().then(digest).then(bufferToBase64).then(base64URLEncode);\n }\n};\n\ndigest = function(data) {\n return crypto.subtle.digest(\"SHA-256\", data);\n};\n\nbase64URLEncode = function(base64String) {\n return base64String.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/\\=/g, \"\");\n};\n\nbufferToBase64 = function(buffer) {\n return window.btoa(String.fromCharCode.apply(null, new Uint8Array(buffer)));\n};\n"
},
"lib/util/marked": {
"content": "/**\r\n * marked - a markdown parser\r\n * Copyright (c) 2011-2019, Christopher Jeffrey. (MIT Licensed)\r\n * https://github.com/markedjs/marked\r\n */\r\n!function(e,t){\"object\"==typeof exports&&\"undefined\"!=typeof module?module.exports=t():\"function\"==typeof define&&define.amd?define(t):(e=e||self).marked=t()}(this,function(){\"use strict\";function r(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function t(e,t,n){return t&&r(e.prototype,t),n&&r(e,n),e}function n(e){return h[e]}var e,s=(function(t){function e(){return{baseUrl:null,breaks:!1,gfm:!0,headerIds:!0,headerPrefix:\"\",highlight:null,langPrefix:\"language-\",mangle:!0,pedantic:!1,renderer:null,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,xhtml:!1}}t.exports={defaults:e(),getDefaults:e,changeDefaults:function(e){t.exports.defaults=e}}}(e={exports:{}},e.exports),e.exports),i=(s.defaults,s.getDefaults,s.changeDefaults,/[&<>\"']/),l=/[&<>\"']/g,a=/[<>\"']|&(?!#?\\w+;)/,o=/[<>\"']|&(?!#?\\w+;)/g,h={\"&\":\"&amp;\",\"<\":\"&lt;\",\">\":\"&gt;\",'\"':\"&quot;\",\"'\":\"&#39;\"};var u=/&(#(?:\\d+)|(?:#x[0-9A-Fa-f]+)|(?:\\w+));?/gi;function c(e){return e.replace(u,function(e,t){return\"colon\"===(t=t.toLowerCase())?\":\":\"#\"===t.charAt(0)?\"x\"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):\"\"})}var p=/(^|[^\\[])\\^/g;var g=/[^\\w:]/g,f=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;var d={},k=/^[^:]+:\\/*[^/]*$/,b=/^([^:]+:)[\\s\\S]*$/,m=/^([^:]+:\\/*[^/]*)[\\s\\S]*$/;function x(e,t){d[\" \"+e]||(k.test(e)?d[\" \"+e]=e+\"/\":d[\" \"+e]=_(e,\"/\",!0));var n=-1===(e=d[\" \"+e]).indexOf(\":\");return\"//\"===t.substring(0,2)?n?t:e.replace(b,\"$1\")+t:\"/\"===t.charAt(0)?n?t:e.replace(m,\"$1\")+t:e+t}function _(e,t,n){var r=e.length;if(0===r)return\"\";for(var s=0;s<r;){var i=e.charAt(r-s-1);if(i!==t||n){if(i===t||!n)break;s++}else s++}return e.substr(0,r-s)}var y=function(e,t){if(t){if(i.test(e))return e.replace(l,n)}else if(a.test(e))return e.replace(o,n);return e},w=c,v=function(e,t,n){if(e){var r;try{r=decodeURIComponent(c(n)).replace(g,\"\").toLowerCase()}catch(e){return null}if(0===r.indexOf(\"javascript:\")||0===r.indexOf(\"vbscript:\")||0===r.indexOf(\"data:\"))return null}t&&!f.test(n)&&(n=x(t,n));try{n=encodeURI(n).replace(/%25/g,\"%\")}catch(e){return null}return n},$=function(e){for(var t,n,r=1;r<arguments.length;r++)for(n in t=arguments[r])Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e},S=function(e,t){var n=e.replace(/\\|/g,function(e,t,n){for(var r=!1,s=t;0<=--s&&\"\\\\\"===n[s];)r=!r;return r?\"|\":\" |\"}).split(/ \\|/),r=0;if(n.length>t)n.splice(t);else for(;n.length<t;)n.push(\"\");for(;r<n.length;r++)n[r]=n[r].trim().replace(/\\\\\\|/g,\"|\");return n},z=_,A=function(e,t){if(-1===e.indexOf(t[1]))return-1;for(var n=e.length,r=0,s=0;s<n;s++)if(\"\\\\\"===e[s])s++;else if(e[s]===t[0])r++;else if(e[s]===t[1]&&--r<0)return s;return-1},R=function(e){e&&e.sanitize&&!e.silent&&console.warn(\"marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options\")},Z={exec:function(){}},L=function(n,e){n=n.source||n,e=e||\"\";var r={replace:function(e,t){return t=(t=t.source||t).replace(p,\"$1\"),n=n.replace(e,t),r},getRegex:function(){return new RegExp(n,e)}};return r},q=$,C={newline:/^\\n+/,code:/^( {4}[^\\n]+\\n*)+/,fences:/^ {0,3}(`{3,}|~{3,})([^`~\\n]*)\\n(?:|([\\s\\S]*?)\\n)(?: {0,3}\\1[~`]* *(?:\\n+|$)|$)/,hr:/^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)/,heading:/^ {0,3}(#{1,6}) +([^\\n]*?)(?: +#+)? *(?:\\n+|$)/,blockquote:/^( {0,3}> ?(paragraph|[^\\n]*)(?:\\n|$))+/,list:/^( {0,3})(bull) [\\s\\S]+?(?:hr|def|\\n{2,}(?! )(?!\\1bull )\\n*|\\s*$)/,html:\"^ {0,3}(?:<(script|pre|style)[\\\\s>][\\\\s\\\\S]*?(?:</\\\\1>[^\\\\n]*\\\\n+|$)|comment[^\\\\n]*(\\\\n+|$)|<\\\\?[\\\\s\\\\S]*?\\\\?>\\\\n*|<![A-Z][\\\\s\\\\S]*?>\\\\n*|<!\\\\[CDATA\\\\[[\\\\s\\\\S]*?\\\\]\\\\]>\\\\n*|</?(tag)(?: +|\\\\n|/?>)[\\\\s\\\\S]*?(?:\\\\n{2,}|$)|<(?!script|pre|style)([a-z][\\\\w-]*)(?:attribute)*? */?>(?=[ \\\\t]*(?:\\\\n|$))[\\\\s\\\\S]*?(?:\\\\n{2,}|$)|</(?!script|pre|style)[a-z][\\\\w-]*\\\\s*>(?=[ \\\\t]*(?:\\\\n|$))[\\\\s\\\\S]*?(?:\\\\n{2,}|$))\",def:/^ {0,3}\\[(label)\\]: *\\n? *<?([^\\s>]+)>?(?:(?: +\\n? *| *\\n *)(title))? *(?:\\n+|$)/,nptable:Z,table:Z,lheading:/^([^\\n]+)\\n {0,3}(=+|-+) *(?:\\n+|$)/,_paragraph:/^([^\\n]+(?:\\n(?!hr|heading|lheading|blockquote|fences|list|html)[^\\n]+)*)/,text:/^[^\\n]+/,_label:/(?!\\s*\\])(?:\\\\[\\[\\]]|[^\\[\\]])+/,_title:/(?:\"(?:\\\\\"?|[^\"\\\\])*\"|'[^'\\n]*(?:\\n[^'\\n]+)*\\n?'|\\([^()]*\\))/};C.def=L(C.def).replace(\"label\",C._label).replace(\"title\",C._title).getRegex(),C.bullet=/(?:[*+-]|\\d{1,9}\\.)/,C.item=/^( *)(bull) ?[^\\n]*(?:\\n(?!\\1bull ?)[^\\n]*)*/,C.item=L(C.item,\"gm\").replace(/bull/g,C.bullet).getRegex(),C.list=L(C.list).replace(/bull/g,C.bullet).replace(\"hr\",\"\\\\n+(?=\\\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\\\* *){3,})(?:\\\\n+|$))\").replace(\"def\",\"\\\\n+(?=\"+C.def.source+\")\").getRegex(),C._tag=\"address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul\",C._comment=/<!--(?!-?>)[\\s\\S]*?-->/,C.html=L(C.html,\"i\").replace(\"comment\",C._comment).replace(\"tag\",C._tag).replace(\"attribute\",/ +[a-zA-Z:_][\\w.:-]*(?: *= *\"[^\"\\n]*\"| *= *'[^'\\n]*'| *= *[^\\s\"'=<>`]+)?/).getRegex(),C.paragraph=L(C._paragraph).replace(\"hr\",C.hr).replace(\"heading\",\" {0,3}#{1,6} +\").replace(\"|lheading\",\"\").replace(\"blockquote\",\" {0,3}>\").replace(\"fences\",\" {0,3}(?:`{3,}|~{3,})[^`\\\\n]*\\\\n\").replace(\"list\",\" {0,3}(?:[*+-]|1[.)]) \").replace(\"html\",\"</?(?:tag)(?: +|\\\\n|/?>)|<(?:script|pre|style|!--)\").replace(\"tag\",C._tag).getRegex(),C.blockquote=L(C.blockquote).replace(\"paragraph\",C.paragraph).getRegex(),C.normal=q({},C),C.gfm=q({},C.normal,{nptable:/^ *([^|\\n ].*\\|.*)\\n *([-:]+ *\\|[-| :]*)(?:\\n((?:.*[^>\\n ].*(?:\\n|$))*)\\n*|$)/,table:/^ *\\|(.+)\\n *\\|?( *[-:]+[-| :]*)(?:\\n((?: *[^>\\n ].*(?:\\n|$))*)\\n*|$)/}),C.pedantic=q({},C.normal,{html:L(\"^ *(?:comment *(?:\\\\n|\\\\s*$)|<(tag)[\\\\s\\\\S]+?</\\\\1> *(?:\\\\n{2,}|\\\\s*$)|<tag(?:\\\"[^\\\"]*\\\"|'[^']*'|\\\\s[^'\\\"/>\\\\s]*)*?/?> *(?:\\\\n{2,}|\\\\s*$))\").replace(\"comment\",C._comment).replace(/tag/g,\"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\\\b)\\\\w+(?!:|[^\\\\w\\\\s@]*@)\\\\b\").getRegex(),def:/^ *\\[([^\\]]+)\\]: *<?([^\\s>]+)>?(?: +([\"(][^\\n]+[\")]))? *(?:\\n+|$)/,heading:/^ *(#{1,6}) *([^\\n]+?) *(?:#+ *)?(?:\\n+|$)/,fences:Z,paragraph:L(C.normal._paragraph).replace(\"hr\",C.hr).replace(\"heading\",\" *#{1,6} *[^\\n]\").replace(\"lheading\",C.lheading).replace(\"blockquote\",\" {0,3}>\").replace(\"|fences\",\"\").replace(\"|list\",\"\").replace(\"|html\",\"\").getRegex()});var O={escape:/^\\\\([!\"#$%&'()*+,\\-./:;<=>?@\\[\\]\\\\^_`{|}~])/,autolink:/^<(scheme:[^\\s\\x00-\\x1f<>]*|email)>/,url:Z,tag:\"^comment|^</[a-zA-Z][\\\\w:-]*\\\\s*>|^<[a-zA-Z][\\\\w-]*(?:attribute)*?\\\\s*/?>|^<\\\\?[\\\\s\\\\S]*?\\\\?>|^<![a-zA-Z]+\\\\s[\\\\s\\\\S]*?>|^<!\\\\[CDATA\\\\[[\\\\s\\\\S]*?\\\\]\\\\]>\",link:/^!?\\[(label)\\]\\(\\s*(href)(?:\\s+(title))?\\s*\\)/,reflink:/^!?\\[(label)\\]\\[(?!\\s*\\])((?:\\\\[\\[\\]]?|[^\\[\\]\\\\])+)\\]/,nolink:/^!?\\[(?!\\s*\\])((?:\\[[^\\[\\]]*\\]|\\\\[\\[\\]]|[^\\[\\]])*)\\](?:\\[\\])?/,strong:/^__([^\\s_])__(?!_)|^\\*\\*([^\\s*])\\*\\*(?!\\*)|^__([^\\s][\\s\\S]*?[^\\s])__(?!_)|^\\*\\*([^\\s][\\s\\S]*?[^\\s])\\*\\*(?!\\*)/,em:/^_([^\\s_])_(?!_)|^\\*([^\\s*<\\[])\\*(?!\\*)|^_([^\\s<][\\s\\S]*?[^\\s_])_(?!_|[^\\spunctuation])|^_([^\\s_<][\\s\\S]*?[^\\s])_(?!_|[^\\spunctuation])|^\\*([^\\s<\"][\\s\\S]*?[^\\s\\*])\\*(?!\\*|[^\\spunctuation])|^\\*([^\\s*\"<\\[][\\s\\S]*?[^\\s])\\*(?!\\*)/,code:/^(`+)([^`]|[^`][\\s\\S]*?[^`])\\1(?!`)/,br:/^( {2,}|\\\\)\\n(?!\\s*$)/,del:Z,text:/^(`+|[^`])(?:[\\s\\S]*?(?:(?=[\\\\<!\\[`*]|\\b_|$)|[^ ](?= {2,}\\n))|(?= {2,}\\n))/,_punctuation:\"!\\\"#$%&'()*+,\\\\-./:;<=>?@\\\\[^_{|}~\"};O.em=L(O.em).replace(/punctuation/g,O._punctuation).getRegex(),O._escapes=/\\\\([!\"#$%&'()*+,\\-./:;<=>?@\\[\\]\\\\^_`{|}~])/g,O._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,O._email=/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,O.autolink=L(O.autolink).replace(\"scheme\",O._scheme).replace(\"email\",O._email).getRegex(),O._attribute=/\\s+[a-zA-Z:_][\\w.:-]*(?:\\s*=\\s*\"[^\"]*\"|\\s*=\\s*'[^']*'|\\s*=\\s*[^\\s\"'=<>`]+)?/,O.tag=L(O.tag).replace(\"comment\",C._comment).replace(\"attribute\",O._attribute).getRegex(),O._label=/(?:\\[[^\\[\\]]*\\]|\\\\.|`[^`]*`|[^\\[\\]\\\\`])*?/,O._href=/<(?:\\\\[<>]?|[^\\s<>\\\\])*>|[^\\s\\x00-\\x1f]*/,O._title=/\"(?:\\\\\"?|[^\"\\\\])*\"|'(?:\\\\'?|[^'\\\\])*'|\\((?:\\\\\\)?|[^)\\\\])*\\)/,O.link=L(O.link).replace(\"label\",O._label).replace(\"href\",O._href).replace(\"title\",O._title).getRegex(),O.reflink=L(O.reflink).replace(\"label\",O._label).getRegex(),O.normal=q({},O),O.pedantic=q({},O.normal,{strong:/^__(?=\\S)([\\s\\S]*?\\S)__(?!_)|^\\*\\*(?=\\S)([\\s\\S]*?\\S)\\*\\*(?!\\*)/,em:/^_(?=\\S)([\\s\\S]*?\\S)_(?!_)|^\\*(?=\\S)([\\s\\S]*?\\S)\\*(?!\\*)/,link:L(/^!?\\[(label)\\]\\((.*?)\\)/).replace(\"label\",O._label).getRegex(),reflink:L(/^!?\\[(label)\\]\\s*\\[([^\\]]*)\\]/).replace(\"label\",O._label).getRegex()}),O.gfm=q({},O.normal,{escape:L(O.escape).replace(\"])\",\"~|])\").getRegex(),_extended_email:/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,url:/^((?:ftp|https?):\\/\\/|www\\.)(?:[a-zA-Z0-9\\-]+\\.?)+[^\\s<]*|^email/,_backpedal:/(?:[^?!.,:;*_~()&]+|\\([^)]*\\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,del:/^~+(?=\\S)([\\s\\S]*?\\S)~+/,text:/^(`+|[^`])(?:[\\s\\S]*?(?:(?=[\\\\<!\\[`*~]|\\b_|https?:\\/\\/|ftp:\\/\\/|www\\.|$)|[^ ](?= {2,}\\n)|[^a-zA-Z0-9.!#$%&'*+\\/=?_`{\\|}~-](?=[a-zA-Z0-9.!#$%&'*+\\/=?_`{\\|}~-]+@))|(?= {2,}\\n|[a-zA-Z0-9.!#$%&'*+\\/=?_`{\\|}~-]+@))/}),O.gfm.url=L(O.gfm.url,\"i\").replace(\"email\",O.gfm._extended_email).getRegex(),O.breaks=q({},O.gfm,{br:L(O.br).replace(\"{2,}\",\"*\").getRegex(),text:L(O.gfm.text).replace(\"\\\\b_\",\"\\\\b_| {2,}\\\\n\").replace(/\\{2,\\}/g,\"*\").getRegex()});var D={block:C,inline:O},E=s.defaults,j=D.block,P=z,T=S,I=y,B=function(){function n(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||E,this.rules=j.normal,this.options.pedantic?this.rules=j.pedantic:this.options.gfm&&(this.rules=j.gfm)}n.lex=function(e,t){return new n(t).lex(e)};var e=n.prototype;return e.lex=function(e){return e=e.replace(/\\r\\n|\\r/g,\"\\n\").replace(/\\t/g,\" \"),this.token(e,!0)},e.token=function(e,t){var n,r,s,i,l,a,o,h,u,c,p,g,f,d,k,b;for(e=e.replace(/^ +$/gm,\"\");e;)if((s=this.rules.newline.exec(e))&&(e=e.substring(s[0].length),1<s[0].length&&this.tokens.push({type:\"space\"})),s=this.rules.code.exec(e)){var m=this.tokens[this.tokens.length-1];e=e.substring(s[0].length),m&&\"paragraph\"===m.type?m.text+=\"\\n\"+s[0].trimRight():(s=s[0].replace(/^ {4}/gm,\"\"),this.tokens.push({type:\"code\",codeBlockStyle:\"indented\",text:this.options.pedantic?s:P(s,\"\\n\")}))}else if(s=this.rules.fences.exec(e))e=e.substring(s[0].length),this.tokens.push({type:\"code\",lang:s[2]?s[2].trim():s[2],text:s[3]||\"\"});else if(s=this.rules.heading.exec(e))e=e.substring(s[0].length),this.tokens.push({type:\"heading\",depth:s[1].length,text:s[2]});else if((s=this.rules.nptable.exec(e))&&(a={type:\"table\",header:T(s[1].replace(/^ *| *\\| *$/g,\"\")),align:s[2].replace(/^ *|\\| *$/g,\"\").split(/ *\\| */),cells:s[3]?s[3].replace(/\\n$/,\"\").split(\"\\n\"):[]}).header.length===a.align.length){for(e=e.substring(s[0].length),p=0;p<a.align.length;p++)/^ *-+: *$/.test(a.align[p])?a.align[p]=\"right\":/^ *:-+: *$/.test(a.align[p])?a.align[p]=\"center\":/^ *:-+ *$/.test(a.align[p])?a.align[p]=\"left\":a.align[p]=null;for(p=0;p<a.cells.length;p++)a.cells[p]=T(a.cells[p],a.header.length);this.tokens.push(a)}else if(s=this.rules.hr.exec(e))e=e.substring(s[0].length),this.tokens.push({type:\"hr\"});else if(s=this.rules.blockquote.exec(e))e=e.substring(s[0].length),this.tokens.push({type:\"blockquote_start\"}),s=s[0].replace(/^ *> ?/gm,\"\"),this.token(s,t),this.tokens.push({type:\"blockquote_end\"});else if(s=this.rules.list.exec(e)){for(e=e.substring(s[0].length),o={type:\"list_start\",ordered:d=1<(i=s[2]).length,start:d?+i:\"\",loose:!1},this.tokens.push(o),n=!(h=[]),f=(s=s[0].match(this.rules.item)).length,p=0;p<f;p++)c=(a=s[p]).length,~(a=a.replace(/^ *([*+-]|\\d+\\.) */,\"\")).indexOf(\"\\n \")&&(c-=a.length,a=this.options.pedantic?a.replace(/^ {1,4}/gm,\"\"):a.replace(new RegExp(\"^ {1,\"+c+\"}\",\"gm\"),\"\")),p!==f-1&&(l=j.bullet.exec(s[p+1])[0],(1<i.length?1===l.length:1<l.length||this.options.smartLists&&l!==i)&&(e=s.slice(p+1).join(\"\\n\")+e,p=f-1)),r=n||/\\n\\n(?!\\s*$)/.test(a),p!==f-1&&(n=\"\\n\"===a.charAt(a.length-1),r=r||n),r&&(o.loose=!0),b=void 0,(k=/^\\[[ xX]\\] /.test(a))&&(b=\" \"!==a[1],a=a.replace(/^\\[[ xX]\\] +/,\"\")),u={type:\"list_item_start\",task:k,checked:b,loose:r},h.push(u),this.tokens.push(u),this.token(a,!1),this.tokens.push({type:\"list_item_end\"});if(o.loose)for(f=h.length,p=0;p<f;p++)h[p].loose=!0;this.tokens.push({type:\"list_end\"})}else if(s=this.rules.html.exec(e))e=e.substring(s[0].length),this.tokens.push({type:this.options.sanitize?\"paragraph\":\"html\",pre:!this.options.sanitizer&&(\"pre\"===s[1]||\"script\"===s[1]||\"style\"===s[1]),text:this.options.sanitize?this.options.sanitizer?this.options.sanitizer(s[0]):I(s[0]):s[0]});else if(t&&(s=this.rules.def.exec(e)))e=e.substring(s[0].length),s[3]&&(s[3]=s[3].substring(1,s[3].length-1)),g=s[1].toLowerCase().replace(/\\s+/g,\" \"),this.tokens.links[g]||(this.tokens.links[g]={href:s[2],title:s[3]});else if((s=this.rules.table.exec(e))&&(a={type:\"table\",header:T(s[1].replace(/^ *| *\\| *$/g,\"\")),align:s[2].replace(/^ *|\\| *$/g,\"\").split(/ *\\| */),cells:s[3]?s[3].replace(/\\n$/,\"\").split(\"\\n\"):[]}).header.length===a.align.length){for(e=e.substring(s[0].length),p=0;p<a.align.length;p++)/^ *-+: *$/.test(a.align[p])?a.align[p]=\"right\":/^ *:-+: *$/.test(a.align[p])?a.align[p]=\"center\":/^ *:-+ *$/.test(a.align[p])?a.align[p]=\"left\":a.align[p]=null;for(p=0;p<a.cells.length;p++)a.cells[p]=T(a.cells[p].replace(/^ *\\| *| *\\| *$/g,\"\"),a.header.length);this.tokens.push(a)}else if(s=this.rules.lheading.exec(e))e=e.substring(s[0].length),this.tokens.push({type:\"heading\",depth:\"=\"===s[2].charAt(0)?1:2,text:s[1]});else if(t&&(s=this.rules.paragraph.exec(e)))e=e.substring(s[0].length),this.tokens.push({type:\"paragraph\",text:\"\\n\"===s[1].charAt(s[1].length-1)?s[1].slice(0,-1):s[1]});else if(s=this.rules.text.exec(e))e=e.substring(s[0].length),this.tokens.push({type:\"text\",text:s[0]});else if(e)throw new Error(\"Infinite loop on byte: \"+e.charCodeAt(0));return this.tokens},t(n,null,[{key:\"rules\",get:function(){return j}}]),n}(),U=s.defaults,F=v,N=y,X=function(){function e(e){this.options=e||U}var t=e.prototype;return t.code=function(e,t,n){var r=(t||\"\").match(/\\S*/)[0];if(this.options.highlight){var s=this.options.highlight(e,r);null!=s&&s!==e&&(n=!0,e=s)}return r?'<pre><code class=\"'+this.options.langPrefix+N(r,!0)+'\">'+(n?e:N(e,!0))+\"</code></pre>\\n\":\"<pre><code>\"+(n?e:N(e,!0))+\"</code></pre>\"},t.blockquote=function(e){return\"<blockquote>\\n\"+e+\"</blockquote>\\n\"},t.html=function(e){return e},t.heading=function(e,t,n,r){return this.options.headerIds?\"<h\"+t+' id=\"'+this.options.headerPrefix+r.slug(n)+'\">'+e+\"</h\"+t+\">\\n\":\"<h\"+t+\">\"+e+\"</h\"+t+\">\\n\"},t.hr=function(){return this.options.xhtml?\"<hr/>\\n\":\"<hr>\\n\"},t.list=function(e,t,n){var r=t?\"ol\":\"ul\";return\"<\"+r+(t&&1!==n?' start=\"'+n+'\"':\"\")+\">\\n\"+e+\"</\"+r+\">\\n\"},t.listitem=function(e){return\"<li>\"+e+\"</li>\\n\"},t.checkbox=function(e){return\"<input \"+(e?'checked=\"\" ':\"\")+'disabled=\"\" type=\"checkbox\"'+(this.options.xhtml?\" /\":\"\")+\"> \"},t.paragraph=function(e){return\"<p>\"+e+\"</p>\\n\"},t.table=function(e,t){return\"<table>\\n<thead>\\n\"+e+\"</thead>\\n\"+(t=t&&\"<tbody>\"+t+\"</tbody>\")+\"</table>\\n\"},t.tablerow=function(e){return\"<tr>\\n\"+e+\"</tr>\\n\"},t.tablecell=function(e,t){var n=t.header?\"th\":\"td\";return(t.align?\"<\"+n+' align=\"'+t.align+'\">':\"<\"+n+\">\")+e+\"</\"+n+\">\\n\"},t.strong=function(e){return\"<strong>\"+e+\"</strong>\"},t.em=function(e){return\"<em>\"+e+\"</em>\"},t.codespan=function(e){return\"<code>\"+e+\"</code>\"},t.br=function(){return this.options.xhtml?\"<br/>\":\"<br>\"},t.del=function(e){return\"<del>\"+e+\"</del>\"},t.link=function(e,t,n){if(null===(e=F(this.options.sanitize,this.options.baseUrl,e)))return n;var r='<a href=\"'+N(e)+'\"';return t&&(r+=' title=\"'+t+'\"'),r+=\">\"+n+\"</a>\"},t.image=function(e,t,n){if(null===(e=F(this.options.sanitize,this.options.baseUrl,e)))return n;var r='<img src=\"'+e+'\" alt=\"'+n+'\"';return t&&(r+=' title=\"'+t+'\"'),r+=this.options.xhtml?\"/>\":\">\"},t.text=function(e){return e},e}(),G=function(){function e(){this.seen={}}return e.prototype.slug=function(e){var t=e.toLowerCase().trim().replace(/[\\u2000-\\u206F\\u2E00-\\u2E7F\\\\'!\"#$%&()*+,./:;<=>?@[\\]^`{|}~]/g,\"\").replace(/\\s/g,\"-\");if(this.seen.hasOwnProperty(t))for(var n=t;this.seen[n]++,t=n+\"-\"+this.seen[n],this.seen.hasOwnProperty(t););return this.seen[t]=0,t},e}(),M=s.defaults,V=D.inline,H=A,J=y,K=function(){function u(e,t){if(this.options=t||M,this.links=e,this.rules=V.normal,this.options.renderer=this.options.renderer||new X,this.renderer=this.options.renderer,this.renderer.options=this.options,!this.links)throw new Error(\"Tokens array requires a `links` property.\");this.options.pedantic?this.rules=V.pedantic:this.options.gfm&&(this.options.breaks?this.rules=V.breaks:this.rules=V.gfm)}u.output=function(e,t,n){return new u(t,n).output(e)};var e=u.prototype;return e.output=function(e){for(var t,n,r,s,i,l,a=\"\";e;)if(i=this.rules.escape.exec(e))e=e.substring(i[0].length),a+=J(i[1]);else if(i=this.rules.tag.exec(e))!this.inLink&&/^<a /i.test(i[0])?this.inLink=!0:this.inLink&&/^<\\/a>/i.test(i[0])&&(this.inLink=!1),!this.inRawBlock&&/^<(pre|code|kbd|script)(\\s|>)/i.test(i[0])?this.inRawBlock=!0:this.inRawBlock&&/^<\\/(pre|code|kbd|script)(\\s|>)/i.test(i[0])&&(this.inRawBlock=!1),e=e.substring(i[0].length),a+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(i[0]):J(i[0]):i[0];else if(i=this.rules.link.exec(e)){var o=H(i[2],\"()\");if(-1<o){var h=(0===i[0].indexOf(\"!\")?5:4)+i[1].length+o;i[2]=i[2].substring(0,o),i[0]=i[0].substring(0,h).trim(),i[3]=\"\"}e=e.substring(i[0].length),this.inLink=!0,r=i[2],s=this.options.pedantic?(t=/^([^'\"]*[^\\s])\\s+(['\"])(.*)\\2/.exec(r))?(r=t[1],t[3]):\"\":i[3]?i[3].slice(1,-1):\"\",r=r.trim().replace(/^<([\\s\\S]*)>$/,\"$1\"),a+=this.outputLink(i,{href:u.escapes(r),title:u.escapes(s)}),this.inLink=!1}else if((i=this.rules.reflink.exec(e))||(i=this.rules.nolink.exec(e))){if(e=e.substring(i[0].length),t=(i[2]||i[1]).replace(/\\s+/g,\" \"),!(t=this.links[t.toLowerCase()])||!t.href){a+=i[0].charAt(0),e=i[0].substring(1)+e;continue}this.inLink=!0,a+=this.outputLink(i,t),this.inLink=!1}else if(i=this.rules.strong.exec(e))e=e.substring(i[0].length),a+=this.renderer.strong(this.output(i[4]||i[3]||i[2]||i[1]));else if(i=this.rules.em.exec(e))e=e.substring(i[0].length),a+=this.renderer.em(this.output(i[6]||i[5]||i[4]||i[3]||i[2]||i[1]));else if(i=this.rules.code.exec(e))e=e.substring(i[0].length),a+=this.renderer.codespan(J(i[2].trim(),!0));else if(i=this.rules.br.exec(e))e=e.substring(i[0].length),a+=this.renderer.br();else if(i=this.rules.del.exec(e))e=e.substring(i[0].length),a+=this.renderer.del(this.output(i[1]));else if(i=this.rules.autolink.exec(e))e=e.substring(i[0].length),r=\"@\"===i[2]?\"mailto:\"+(n=J(this.mangle(i[1]))):n=J(i[1]),a+=this.renderer.link(r,null,n);else if(this.inLink||!(i=this.rules.url.exec(e))){if(i=this.rules.text.exec(e))e=e.substring(i[0].length),this.inRawBlock?a+=this.renderer.text(this.options.sanitize?this.options.sanitizer?this.options.sanitizer(i[0]):J(i[0]):i[0]):a+=this.renderer.text(J(this.smartypants(i[0])));else if(e)throw new Error(\"Infinite loop on byte: \"+e.charCodeAt(0))}else{if(\"@\"===i[2])r=\"mailto:\"+(n=J(i[0]));else{for(;l=i[0],i[0]=this.rules._backpedal.exec(i[0])[0],l!==i[0];);n=J(i[0]),r=\"www.\"===i[1]?\"http://\"+n:n}e=e.substring(i[0].length),a+=this.renderer.link(r,null,n)}return a},u.escapes=function(e){return e?e.replace(u.rules._escapes,\"$1\"):e},e.outputLink=function(e,t){var n=t.href,r=t.title?J(t.title):null;return\"!\"!==e[0].charAt(0)?this.renderer.link(n,r,this.output(e[1])):this.renderer.image(n,r,J(e[1]))},e.smartypants=function(e){return this.options.smartypants?e.replace(/---/g,\"—\").replace(/--/g,\"\").replace(/(^|[-\\u2014/(\\[{\"\\s])'/g,\"$1\").replace(/'/g,\"\").replace(/(^|[-\\u2014/(\\[{\\u2018\\s])\"/g,\"$1“\").replace(/\"/g,\"”\").replace(/\\.{3}/g,\"…\"):e},e.mangle=function(e){if(!this.options.mangle)return e;for(var t,n=e.length,r=\"\",s=0;s<n;s++)t=e.charCodeAt(s),.5<Math.random()&&(t=\"x\"+t.toString(16)),r+=\"&#\"+t+\";\";return r},t(u,null,[{key:\"rules\",get:function(){return V}}]),u}(),Q=function(){function e(){}var t=e.prototype;return t.strong=function(e){return e},t.em=function(e){return e},t.codespan=function(e){return e},t.del=function(e){return e},t.text=function(e){return e},t.link=function(e,t,n){return\"\"+n},t.image=function(e,t,n){return\"\"+n},t.br=function(){return\"\"},e}(),W=s.defaults,Y=$,ee=w,te=function(){function n(e){this.tokens=[],this.token=null,this.options=e||W,this.options.renderer=this.options.renderer||new X,this.renderer=this.options.renderer,this.renderer.options=this.options,this.slugger=new G}n.parse=function(e,t){return new n(t).parse(e)};var e=n.prototype;return e.parse=function(e){this.inline=new K(e.links,this.options),this.inlineText=new K(e.links,Y({},this.options,{renderer:new Q})),this.tokens=e.reverse();for(var t=\"\";this.next();)t+=this.tok();return t},e.next=function(){return this.token=this.tokens.pop(),this.token},e.peek=function(){return this.tokens[this.tokens.length-1]||0},e.parseText=function(){for(var e=this.token.text;\"text\"===this.peek().type;)e+=\"\\n\"+this.next().text;return this.inline.output(e)},e.tok=function(){var e=\"\";switch(this.token.type){case\"space\":return\"\";case\"hr\":return this.renderer.hr();case\"heading\":return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,ee(this.inlineText.output(this.token.text)),this.slugger);case\"code\":return this.renderer.code(this.token.text,this.token.lang,this.token.escaped);case\"table\":var t,n,r,s,i=\"\";for(r=\"\",t=0;t<this.token.header.length;t++)r+=this.renderer.tablecell(this.inline.output(this.token.header[t]),{header:!0,align:this.token.align[t]});for(i+=this.renderer.tablerow(r),t=0;t<this.token.cells.length;t++){for(n=this.token.cells[t],r=\"\",s=0;s<n.length;s++)r+=this.renderer.tablecell(this.inline.output(n[s]),{header:!1,align:this.token.align[s]});e+=this.renderer.tablerow(r)}return this.renderer.table(i,e);case\"blockquote_start\":for(e=\"\";\"blockquote_end\"!==this.next().type;)e+=this.tok();return this.renderer.blockquote(e);case\"list_start\":e=\"\";for(var l=this.token.ordered,a=this.token.start;\"list_end\"!==this.next().type;)e+=this.tok();return this.renderer.list(e,l,a);case\"list_item_start\":e=\"\";var o=this.token.loose,h=this.token.checked,u=this.token.task;if(this.token.task)if(o)if(\"text\"===this.peek().type){var c=this.peek();c.text=this.renderer.checkbox(h)+\" \"+c.text}else this.tokens.push({type:\"text\",text:this.renderer.checkbox(h)});else e+=this.renderer.checkbox(h);for(;\"list_item_end\"!==this.next().type;)e+=o||\"text\"!==this.token.type?this.tok():this.parseText();return this.renderer.listitem(e,u,h);case\"html\":return this.renderer.html(this.token.text);case\"paragraph\":return this.renderer.paragraph(this.inline.output(this.token.text));case\"text\":return this.renderer.paragraph(this.parseText());default:var p='Token with \"'+this.token.type+'\" type was not found.';if(!this.options.silent)throw new Error(p);console.log(p)}},n}(),ne=$,re=R,se=y,ie=s.getDefaults,le=s.changeDefaults,ae=s.defaults;function oe(t,l,a){if(null==t)throw new Error(\"marked(): input parameter is undefined or null\");if(\"string\"!=typeof t)throw new Error(\"marked(): input parameter is of type \"+Object.prototype.toString.call(t)+\", string expected\");if(a||\"function\"==typeof l){var e=function(){a||(a=l,l=null),l=ne({},oe.defaults,l||{}),re(l);var n,r,s=l.highlight,e=0;try{n=B.lex(t,l)}catch(e){return{v:a(e)}}r=n.length;function i(t){if(t)return l.highlight=s,a(t);var e;try{e=te.parse(n,l)}catch(e){t=e}return l.highlight=s,t?a(t):a(null,e)}if(!s||s.length<3)return{v:i()};if(delete l.highlight,!r)return{v:i()};for(;e<n.length;e++)!function(n){\"code\"!==n.type?--r||i():s(n.text,n.lang,function(e,t){return e?i(e):null==t||t===n.text?--r||i():(n.text=t,n.escaped=!0,void(--r||i()))})}(n[e]);return{v:void 0}}();if(\"object\"==typeof e)return e.v}try{return l=ne({},oe.defaults,l||{}),re(l),te.parse(B.lex(t,l),l)}catch(e){if(e.message+=\"\\nPlease report this to https://github.com/markedjs/marked.\",(l||oe.defaults).silent)return\"<p>An error occurred:</p><pre>\"+se(e.message+\"\",!0)+\"</pre>\";throw e}}return oe.options=oe.setOptions=function(e){return ne(oe.defaults,e),le(oe.defaults),oe},oe.getDefaults=ie,oe.defaults=ae,oe.Parser=te,oe.parser=te.parse,oe.Renderer=X,oe.TextRenderer=Q,oe.Lexer=B,oe.lexer=B.lex,oe.InlineLexer=K,oe.inlineLexer=K.output,oe.Slugger=G,oe.parse=oe});\n"
},
"lib/pkg/index": {
"content": "var dependencyScripts, html, htmlToBlob, systemWrap;\n\nmodule.exports = {\n\n /*\n Parse a dependency string to a URL.\n */\n parseDependencyPath: function(string, registry) {\n var branch, match, name, ref, repo, user, _;\n if (registry == null) {\n registry = \"https://whimsy.space/apps/\";\n }\n if (string.match(/^https\\:/)) {\n return string;\n } else if ((match = string.match(/^([^\\/\\:]*)\\:(.*)$/))) {\n _ = match[0], name = match[1], ref = match[2];\n return \"\" + registry + name + \"/\" + ref + \".json\";\n } else if ((match = string.match(/^([^\\/]*)\\/([^\\:]*)\\:(.*)$/))) {\n _ = match[0], user = match[1], repo = match[2], branch = match[3];\n return \"https://\" + user + \".github.io/\" + repo + \"/\" + branch + \".json\";\n } else {\n throw new Error(\"Failed to parse repository info string \" + string + \"\\n\\nValid forms include:\\n https URLs\\n examples: \\n https://danielx.net/editor/master.json\\n https://whimsy.space/issue-2.json\\n\\n <pkg>:<ref>\\n examples: \\n postmaster:v0.6.1\\n issues:master\");\n }\n },\n\n /*\n Construct an HTML file for the package.\n \n The default behavior is to load the package's entry point but\n that can be modified enter from any file in the package.\n \n It also adds remote dependencies to the HTML head and wraps with\n the system launch if present.\n \n This is designed to be simple and general, any magic binding should happen in\n the `!system` layer. Here we are only concerned with html tags and setting up\n the package with `require`.\n */\n htmlBlob: function(pkg, opts) {\n var code, config, description, favicon, iconURL, lang, systemConfig, title, _ref;\n if (opts == null) {\n opts = {};\n }\n code = opts.code, systemConfig = opts.systemConfig;\n if (code == null) {\n code = \"require\\('./\" + (pkg.entryPoint || 'main') + \"');\";\n }\n config = pkg.config;\n code = systemWrap(pkg, code, systemConfig);\n if (config) {\n title = config.title, description = config.description, lang = config.lang, iconURL = config.iconURL;\n if (description) {\n description = metaTag(\"description\", description.replace(/\\n/g, \" \"));\n } else {\n description = \"\";\n }\n if (iconURL) {\n favicon = \"<link rel=\\\"shortcut icon\\\" href=\" + (JSON.stringify(iconURL)) + \" />\";\n } else {\n favicon = \"\";\n }\n } else {\n title = \"\";\n description = \"\";\n favicon = \"\";\n }\n return html({\n head: \"<title>\" + title + \"</title>\\n\" + description + \"\\n\" + favicon + \"\\n<meta name=\\\"viewport\\\" content=\\\"width=device-width, initial-scale=1.0\\\">\\n\" + (dependencyScripts((_ref = pkg.config) != null ? _ref.remoteDependencies : void 0)),\n body: \"<script>\\n \" + (require.packageWrapper(pkg, code)) + \"\\n<\\/script>\",\n lang: lang\n });\n }\n};\n\nhtml = function(_arg) {\n var body, head, lang;\n head = _arg.head, body = _arg.body, lang = _arg.lang;\n if (lang == null) {\n lang = \"en\";\n }\n if (head == null) {\n head = \"\";\n }\n return htmlToBlob(\"<!DOCTYPE html>\\n<html lang=\" + (JSON.stringify(lang)) + \">\\n <head>\\n <meta charset=\\\"utf-8\\\">\\n \" + head + \"\\n </head>\\n <body>\\n \" + body + \"\\n </body>\\n</html>\");\n};\n\nsystemWrap = function(pkg, code, opts) {\n var _ref;\n if (opts == null) {\n opts = {};\n }\n if ((_ref = pkg.dependencies) != null ? _ref[\"!system\"] : void 0) {\n return \"require(\\\"!system\\\").launch(\" + (JSON.stringify(opts)) + \", function(config) {\\n \" + code + \"\\n});\";\n } else {\n return code;\n }\n};\n\ndependencyScripts = function(remoteDependencies) {\n if (remoteDependencies == null) {\n remoteDependencies = [];\n }\n return remoteDependencies.map(function(src) {\n return \"<script src=\" + (JSON.stringify(src)) + \"><\\/script>\";\n }).join(\"\\n\");\n};\n\nhtmlToBlob = function(html) {\n return new Blob([html], {\n type: \"text/html; charset=utf-8\"\n });\n};\n"
},
"views/ace-editor": {
"content": "\n/*\nAn element containing an ace editor.\n\nDepends on ace and language tools extension being available in the environment.\n */\nvar AceEditor;\n\nmodule.exports = AceEditor = function() {\n var aceEditor, aceElement, self;\n ace.require(\"ace/ext/language_tools\");\n aceElement = document.createElement(\"section\");\n aceEditor = ace.edit(aceElement);\n aceEditor.$blockScrolling = Infinity;\n aceEditor.setOptions({\n fontFamily: \"'Fira Code', 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace\",\n fontSize: \"16px\",\n enableBasicAutocompletion: true,\n enableLiveAutocompletion: true,\n highlightActiveLine: true\n });\n self = {\n aceEditor: aceEditor,\n element: aceElement,\n hidden: function(b) {\n if (b) {\n return aceElement.classList.add(\"hidden\");\n } else {\n return aceElement.classList.remove(\"hidden\");\n }\n },\n goto: function(line, selection) {\n var end, session, start;\n if (line == null) {\n line = 0;\n }\n aceEditor.moveCursorTo(line, 0);\n aceEditor.clearSelection();\n if (selection) {\n session = aceEditor.getSession();\n start = session.doc.indexToPosition(selection.start);\n end = session.doc.indexToPosition(selection.end);\n aceEditor.selection.setSelectionRange({\n start: start,\n end: end\n });\n }\n aceEditor.scrollToLine(line, true, false, function() {});\n },\n setSession: function(session, readOnly) {\n if (readOnly == null) {\n readOnly = false;\n }\n aceEditor.setSession(session);\n aceEditor.setReadOnly(readOnly);\n aceEditor.focus();\n }\n };\n return self;\n};\n\n\n/*\nInitialize a session to track an observable\n */\n\nAceEditor.initAceSession = function(initialContent, contentObservable, mode) {\n var session, updating;\n if (mode == null) {\n mode = \"coffee\";\n }\n if (!contentObservable) {\n return;\n }\n session = ace.createEditSession(initialContent);\n session.setUseWorker(false);\n session.setMode(\"ace/mode/\" + mode);\n session.setUseSoftTabs(true);\n session.setTabSize(2);\n updating = 0;\n contentObservable.observe(function(newContent) {\n if (updating) {\n return;\n }\n updating++;\n session.setValue(newContent);\n return updating--;\n });\n session.on(\"change\", function() {\n if (updating) {\n return;\n }\n updating++;\n contentObservable(session.getValue());\n return updating--;\n });\n return session;\n};\n"
},
"lib/core": {
"content": "\n/*\nModel\n=====\n\nThe `Model` module provides helper methods to compose nested data models.\n */\nvar Model, Observable, defaults, extend, getValue, isFn, setValue,\n __slice = [].slice;\n\nObservable = require(\"./observable\");\n\nmodule.exports = Model = function(I, self) {\n if (I == null) {\n I = {};\n }\n if (self == null) {\n self = {};\n }\n Object.assign(self, {\n\n /*\n `I` holds the instance state. It is generally considered private, but access\n is available for debugging and other purposes.\n */\n I: I,\n\n /*\n Extends this object with methods from the passed in object. A shortcut for Object.extend(self, methods)\n \n > I =\n > x: 30\n > y: 40\n > maxSpeed: 5\n >\n > # we are using extend to give player\n > # additional methods that Model doesn't have\n > player = Model(I).extend\n > increaseSpeed: ->\n > I.maxSpeed += 1\n >\n > player.increaseSpeed()\n */\n extend: function() {\n var objects;\n objects = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n return Object.assign.apply(Object, [self].concat(__slice.call(objects)));\n },\n\n /*\n Includes a module in this object. A module is a constructor that takes two parameters, `I` and `self`\n \n > myObject = Model()\n > myObject.include(Bindable)\n \n > # now you can bind handlers to functions\n > myObject.bind \"someEvent\", ->\n > alert(\"wow. that was easy.\")\n */\n include: function() {\n var Module, modules, _i, _len;\n modules = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n for (_i = 0, _len = modules.length; _i < _len; _i++) {\n Module = modules[_i];\n if (Module.length === 1) {\n Module(self);\n } else {\n Module(I, self);\n }\n }\n return self;\n },\n\n /*\n Bind a data model getter/setter to an attribute. The data model is bound directly to\n the attribute and must be directly convertible to and from JSON.\n */\n attrData: function(name, DataModel) {\n I[name] = DataModel(I[name]);\n return Object.defineProperty(self, name, {\n get: function() {\n return I[name];\n },\n set: function(value) {\n return I[name] = DataModel(value);\n }\n });\n },\n\n /*\n Observe any number of attributes as observables. For each attribute name passed in we expose a public getter/setter method and listen to changes when the value is set.\n */\n attrObservable: function() {\n var names;\n names = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n names.forEach(function(name) {\n self[name] = Observable(I[name]);\n return self[name].observe(function(newValue) {\n return I[name] = newValue;\n });\n });\n return self;\n },\n\n /*\n Observe an attribute as a model. Treats the attribute given as an Observable\n model instance exposing a getter/setter method of the same name. The Model\n constructor must be passed explicitly.\n */\n attrModel: function(name, Model) {\n var model;\n model = Model(I[name]);\n self[name] = Observable(model);\n self[name].observe(function(newValue) {\n return I[name] = newValue.I;\n });\n return self;\n },\n\n /*\n Observe an attribute as an array of sub-models. This is the same as `attrModel`\n except the attribute is expected to be an array of models rather than a single one.\n */\n attrModels: function(name, Model) {\n var models;\n models = (I[name] || []).map(function(x) {\n return Model(x);\n });\n self[name] = Observable(models);\n self[name].observe(function(newValue) {\n return I[name] = newValue.map(function(instance) {\n return instance.I;\n });\n });\n return self;\n },\n\n /*\n Delegate methods to another target. Makes it easier to compose rather than extend.\n */\n delegate: function() {\n var names, to, _arg, _i;\n names = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), _arg = arguments[_i++];\n to = _arg.to;\n return names.forEach(function(name) {\n return Object.defineProperty(self, name, {\n get: function() {\n var receiver;\n receiver = getValue(self, to);\n return receiver[name];\n },\n set: function(value) {\n var receiver;\n receiver = getValue(self, to);\n return setValue(receiver, name, value);\n }\n });\n });\n },\n\n /*\n The JSON representation is kept up to date via the observable properites and resides in `I`.\n */\n toJSON: function() {\n return I;\n }\n });\n return self;\n};\n\nisFn = function(x) {\n return typeof x === 'function';\n};\n\ngetValue = function(receiver, property) {\n if (isFn(receiver[property])) {\n return receiver[property]();\n } else {\n return receiver[property];\n }\n};\n\nsetValue = function(receiver, property, value) {\n var target;\n target = receiver[property];\n if (isFn(target)) {\n return target.call(receiver, value);\n } else {\n return receiver[property] = value;\n }\n};\n\ndefaults = function() {\n var name, object, objects, target, _i, _len;\n target = arguments[0], objects = 2 <= arguments.length ? __slice.call(arguments, 1) : [];\n for (_i = 0, _len = objects.length; _i < _len; _i++) {\n object = objects[_i];\n for (name in object) {\n if (!target.hasOwnProperty(name)) {\n target[name] = object[name];\n }\n }\n }\n return target;\n};\n\nextend = Object.assign;\n\nObject.assign(Model, {\n Observable: Observable,\n defaults: defaults,\n extend: extend\n});\n"
},
"lib/exp/core-state": {
"content": "\n/*\nNOTE: This is experimenting with passing a single `self` reference rather than\n`I`, `self` as parameters. I don't think it is better, but it was worth\ncomparing.\n\nCore\n====\n\n`Core` provides helper methods to compose nested data models. It handles\ndata persistence and state binding. It provides common helpers and extensions\nto expand an object with modules.\n\nBy providing a common way to bind state and compose data we can use Core as a\ncommon building block for many types of objects.\n */\nvar Model, Observable, defaults, extend, getValue, isFn, setValue,\n __slice = [].slice;\n\nObservable = require(\"../observable\");\n\nmodule.exports = Model = function(self) {\n if (self == null) {\n self = {};\n }\n defaults(self, {\n __state: {}\n });\n extend(self, {\n\n /*\n Extends this object with methods from the passed in object. A shortcut for Object.extend(self, methods)\n \n > I =\n > x: 30\n > y: 40\n > maxSpeed: 5\n >\n > # we are using extend to give player\n > # additional methods that Model doesn't have\n > player = Model(I).extend\n > increaseSpeed: ->\n > I.maxSpeed += 1\n >\n > player.increaseSpeed()\n */\n extend: function() {\n var objects;\n objects = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n return extend.apply(null, [self].concat(__slice.call(objects)));\n },\n\n /*\n Includes a module in this object. A module is a constructor that takes one \n parameter `self`. It extends the object with any additional behavior.\n \n > myObject = Model()\n > myObject.include(Bindable)\n \n > # now you can bind handlers to functions\n > myObject.bind \"someEvent\", ->\n > alert(\"wow. that was easy.\")\n */\n include: function() {\n var Module, modules, _i, _len;\n modules = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n for (_i = 0, _len = modules.length; _i < _len; _i++) {\n Module = modules[_i];\n Module(self);\n }\n return self;\n },\n\n /*\n Bind a data model getter/setter to an attribute. The data model is bound directly to\n the attribute and must be directly convertible to and from JSON.\n */\n attrData: function(name, DataModel) {\n self.__state[name] = DataModel(self.__state[name]);\n return Object.defineProperty(self, name, {\n get: function() {\n return self.__state[name];\n },\n set: function(value) {\n return self.__state[name] = DataModel(value);\n }\n });\n },\n\n /*\n Observe any number of attributes as observables. For each attribute name passed in we expose a public getter/setter method and listen to changes when the value is set.\n */\n attrObservable: function() {\n var names;\n names = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n names.forEach(function(name) {\n self[name] = Observable(self.__state[name]);\n return self[name].observe(function(newValue) {\n return self.__state[name] = newValue;\n });\n });\n return self;\n },\n\n /*\n Observe an attribute as a model. Treats the attribute given as an Observable\n model instance exposing a getter/setter method of the same name. The Model\n constructor must be passed explicitly.\n */\n attrModel: function(name, Model) {\n var model;\n model = Model({\n __state: self.__state[name]\n });\n self[name] = Observable(model);\n self[name].observe(function(newValue) {\n return self.__state[name] = newValue.__state;\n });\n return self;\n },\n\n /*\n Observe an attribute as an array of sub-models. This is the same as `attrModel`\n except the attribute is expected to be an array of models rather than a single one.\n */\n attrModels: function(name, Model) {\n var models;\n models = (self.__state[name] || []).map(function(x) {\n return Model({\n __state: x\n });\n });\n self[name] = Observable(models);\n self[name].observe(function(newValue) {\n return self.__state[name] = newValue.map(function(instance) {\n return instance.__state;\n });\n });\n return self;\n },\n\n /*\n Delegate methods to another target. Makes it easier to compose rather than extend.\n */\n delegate: function() {\n var names, to, _arg, _i;\n names = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), _arg = arguments[_i++];\n to = _arg.to;\n return names.forEach(function(name) {\n return Object.defineProperty(self, name, {\n get: function() {\n var receiver;\n receiver = getValue(self, to);\n return receiver[name];\n },\n set: function(value) {\n var receiver;\n receiver = getValue(self, to);\n return setValue(receiver, name, value);\n }\n });\n });\n },\n\n /*\n The JSON representation is kept up to date via the observable properites and resides in `I`.\n */\n toJSON: function() {\n return self.__state;\n }\n });\n return self;\n};\n\nisFn = function(x) {\n return typeof x === 'function';\n};\n\ngetValue = function(receiver, property) {\n if (isFn(receiver[property])) {\n return receiver[property]();\n } else {\n return receiver[property];\n }\n};\n\nsetValue = function(receiver, property, value) {\n var target;\n target = receiver[property];\n if (isFn(target)) {\n return target.call(receiver, value);\n } else {\n return receiver[property] = value;\n }\n};\n\ndefaults = function() {\n var name, object, objects, target, _i, _len;\n target = arguments[0], objects = 2 <= arguments.length ? __slice.call(arguments, 1) : [];\n for (_i = 0, _len = objects.length; _i < _len; _i++) {\n object = objects[_i];\n for (name in object) {\n if (!target.hasOwnProperty(name)) {\n target[name] = object[name];\n }\n }\n }\n return target;\n};\n\nextend = Object.assign;\n\nObject.assign(Model, {\n Observable: Observable,\n defaults: defaults,\n extend: extend\n});\n"
}
},
"progenitor": {
"url": "https://danielx.net/editor/"
},
"config": {
"name": "system",
"version": "0.5.0-pre.4",
"publishPath": "/My Briefcase/public/danielx.net/",
"dependencies": {
"postmaster": "distri/postmaster:master"
},
"remoteDependencies": [
"https://danielx.net/cdn/dexie/2.0.4.min.js",
"https://danielx.whimsy.space/cdn/cognito/sdk.min.js",
"https://danielx.whimsy.space/cdn/cognito/identity.min.js",
"https://sdk.amazonaws.com/js/aws-sdk-2.7.20.min.js"
],
"cognito": {
"identityPoolId": "us-east-1:4fe22da5-bb5e-4a78-a260-74ae0a140bf9",
"poolData": {
"UserPoolId": "us-east-1_cfvrlBLXG",
"ClientId": "3fd84r6idec9iork4e9l43mp61"
}
}
},
"version": "0.2.1",
"repository": {
"branch": "master",
"default_branch": "master",
"full_name": "STRd6/ui",
"homepage": null,
"description": "Classic User Interface",
"html_url": "https://github.com/STRd6/ui",
"url": "https://api.github.com/repos/STRd6/ui",
"publishBranch": "gh-pages"
},
"dependencies": {
"postmaster": {
"distribution": {
"main": {
"path": "main",
"content": "\n/*\n\nPostmaster wraps the `postMessage` API with promises.\n */\n\n(function() {\n var Postmaster, ackTimeout, defaultReceiver, pmId,\n __slice = [].slice;\n\n defaultReceiver = self;\n\n ackTimeout = 1000;\n\n pmId = 0;\n\n module.exports = Postmaster = function(self) {\n var clear, debug, dominant, info, listener, msgId, name, pendingResponses, receiver, send;\n if (self == null) {\n self = {};\n }\n name = \"\" + defaultReceiver.name + \"-\" + (++pmId);\n info = function() {\n var _ref;\n return (_ref = self.logger).info.apply(_ref, [name].concat(__slice.call(arguments)));\n };\n debug = function() {\n var _ref;\n return (_ref = self.logger).debug.apply(_ref, [name].concat(__slice.call(arguments)));\n };\n dominant = Postmaster.dominant();\n if (self.remoteTarget == null) {\n self.remoteTarget = function() {\n return dominant;\n };\n }\n if (self.receiver == null) {\n self.receiver = function() {\n return defaultReceiver;\n };\n }\n if (self.ackTimeout == null) {\n self.ackTimeout = function() {\n return ackTimeout;\n };\n }\n if (self.delegate == null) {\n self.delegate = self;\n }\n if (self.logger == null) {\n self.logger = {\n info: function() {},\n debug: function() {}\n };\n }\n if (self.token == null) {\n self.token = Math.random();\n }\n send = function(data) {\n var target;\n target = self.remoteTarget();\n if (self.token) {\n data.token = self.token;\n }\n data.from = name;\n if (!target) {\n throw new Error(\"No remote target\");\n }\n info(\"->\", data);\n if ((typeof Worker === \"undefined\" || Worker === null) || target instanceof Worker) {\n target.postMessage(data);\n } else {\n target.postMessage(data, \"*\");\n }\n };\n listener = function(event) {\n var data, id, method, params, source, target, type, _ref;\n data = event.data, source = event.source;\n target = self.remoteTarget();\n if (source === target || (source === void 0 && data.token === self.token)) {\n event.stopImmediatePropagation();\n info(\"<-\", data);\n type = data.type, method = data.method, params = data.params, id = data.id;\n switch (type) {\n case \"ack\":\n return (_ref = pendingResponses[id]) != null ? _ref.ack = true : void 0;\n case \"response\":\n return pendingResponses[id].resolve(data.result);\n case \"error\":\n return pendingResponses[id].reject(data.error);\n case \"message\":\n return Promise.resolve().then(function() {\n var _ref1;\n if (source) {\n send({\n type: \"ack\",\n id: id\n });\n }\n if (typeof self.delegate[method] === \"function\") {\n return (_ref1 = self.delegate)[method].apply(_ref1, params);\n } else {\n throw new Error(\"`\" + method + \"` is not a function\");\n }\n }).then(function(result) {\n if (source) {\n return send({\n type: \"response\",\n id: id,\n result: result\n });\n }\n })[\"catch\"](function(error) {\n var message;\n if (typeof error === \"string\") {\n message = error;\n } else {\n message = error.message;\n }\n if (source) {\n return send({\n type: \"error\",\n id: id,\n error: {\n message: message,\n stack: error.stack\n }\n });\n }\n });\n }\n } else {\n return debug(\"DROP message\", event, \"source \" + (JSON.stringify(data.from)) + \" does not match target\");\n }\n };\n receiver = self.receiver();\n receiver.addEventListener(\"message\", listener);\n self.dispose = function() {\n receiver.removeEventListener(\"message\", listener);\n return info(\"DISPOSE\");\n };\n pendingResponses = {};\n msgId = 0;\n clear = function(id) {\n debug(\"CLEAR PENDING\", id);\n clearTimeout(pendingResponses[id].timeout);\n return delete pendingResponses[id];\n };\n self.invokeRemote = function() {\n var method, params;\n method = arguments[0], params = 2 <= arguments.length ? __slice.call(arguments, 1) : [];\n return new Promise(function(resolve, reject) {\n var ackWait, e, id, resp, timeout;\n id = ++msgId;\n ackWait = self.ackTimeout();\n timeout = setTimeout(function() {\n if (!resp.ack) {\n info(\"TIMEOUT\", resp);\n return resp.reject(new Error(\"No ack received within \" + ackWait));\n }\n }, ackWait);\n debug(\"STORE PENDING\", id);\n pendingResponses[id] = resp = {\n timeout: timeout,\n resolve: function(result) {\n debug(\"RESOLVE\", id, result);\n resolve(result);\n return clear(id);\n },\n reject: function(error) {\n debug(\"REJECT\", id, error);\n reject(error);\n return clear(id);\n }\n };\n try {\n return send({\n type: \"message\",\n method: method,\n params: params,\n id: id\n });\n } catch (_error) {\n e = _error;\n reject(e);\n }\n });\n };\n info(\"INITIALIZE\");\n return self;\n };\n\n Postmaster.dominant = function() {\n if (typeof window !== \"undefined\" && window !== null) {\n return opener || ((parent !== window) && parent) || void 0;\n } else {\n return self;\n }\n };\n\n}).call(this);\n",
"type": "blob"
},
"pixie": {
"path": "pixie",
"content": "module.exports = {\"version\":\"0.7.0-pre.13\"};",
"type": "blob"
}
},
"progenitor": {
"url": "https://danielx.net/editor/"
},
"config": {
"version": "0.7.0-pre.13"
},
"version": "0.7.0-pre.13",
"entryPoint": "main",
"repository": {
"branch": "master",
"default_branch": "master",
"full_name": "distri/postmaster",
"homepage": null,
"description": "Send and receive postMessage commands.",
"html_url": "https://github.com/distri/postmaster",
"url": "https://api.github.com/repos/distri/postmaster",
"publishBranch": "gh-pages"
},
"dependencies": {}
}
},
"remoteDependencies": [
"https://danielx.net/cdn/dexie/2.0.4.min.js",
"https://danielx.whimsy.space/cdn/cognito/sdk.min.js",
"https://danielx.whimsy.space/cdn/cognito/identity.min.js",
"https://sdk.amazonaws.com/js/aws-sdk-2.7.20.min.js"
]
}
},
"remoteDependencies": [
"https://js.stripe.com/v3/"
]
});
</script>
</body>
</html>