]*)['"]\s*>([\S\s]*?)<\/yield\s*>/ig,
+ reYieldDest = / |>([\S\s]*?)<\/yield\s*>)/ig,
+ rootEls = { tr: 'tbody', th: 'tr', td: 'tr', col: 'colgroup' },
+ tblTags = IE_VERSION && IE_VERSION < 10 ? RE_SPECIAL_TAGS : RE_SPECIAL_TAGS_NO_OPTION,
+ GENERIC = 'div',
+ SVG = 'svg';
+
+
+ /*
+ Creates the root element for table or select child elements:
+ tr/th/td/thead/tfoot/tbody/caption/col/colgroup/option/optgroup
+ */
+ function specialTags(el, tmpl, tagName) {
+
+ var
+ select = tagName[0] === 'o',
+ parent = select ? 'select>' : 'table>';
+
+ // trim() is important here, this ensures we don't have artifacts,
+ // so we can check if we have only one element inside the parent
+ el.innerHTML = '<' + parent + tmpl.trim() + '' + parent;
+ parent = el.firstChild;
+
+ // returns the immediate parent if tr/th/td/col is the only element, if not
+ // returns the whole tree, as this can include additional elements
+ /* istanbul ignore next */
+ if (select) {
+ parent.selectedIndex = -1; // for IE9, compatible w/current riot behavior
+ } else {
+ // avoids insertion of cointainer inside container (ex: tbody inside tbody)
+ var tname = rootEls[tagName];
+ if (tname && parent.childElementCount === 1) { parent = $(tname, parent); }
+ }
+ return parent
+ }
+
+ /*
+ Replace the yield tag from any tag template with the innerHTML of the
+ original tag in the page
+ */
+ function replaceYield(tmpl, html) {
+ // do nothing if no yield
+ if (!reHasYield.test(tmpl)) { return tmpl }
+
+ // be careful with #1343 - string on the source having `$1`
+ var src = {};
+
+ html = html && html.replace(reYieldSrc, function (_, ref, text) {
+ src[ref] = src[ref] || text; // preserve first definition
+ return ''
+ }).trim();
+
+ return tmpl
+ .replace(reYieldDest, function (_, ref, def) { // yield with from - to attrs
+ return src[ref] || def || ''
+ })
+ .replace(reYieldAll, function (_, def) { // yield without any "from"
+ return html || def || ''
+ })
+ }
+
+ /**
+ * Creates a DOM element to wrap the given content. Normally an `DIV`, but can be
+ * also a `TABLE`, `SELECT`, `TBODY`, `TR`, or `COLGROUP` element.
+ *
+ * @param { String } tmpl - The template coming from the custom tag definition
+ * @param { String } html - HTML content that comes from the DOM element where you
+ * will mount the tag, mostly the original tag in the page
+ * @param { Boolean } isSvg - true if the root node is an svg
+ * @returns { HTMLElement } DOM element with _tmpl_ merged through `YIELD` with the _html_.
+ */
+ function mkdom(tmpl, html, isSvg) {
+ var match = tmpl && tmpl.match(/^\s*<([-\w]+)/);
+ var tagName = match && match[1].toLowerCase();
+ var el = makeElement(isSvg ? SVG : GENERIC);
+
+ // replace all the yield tags with the tag inner html
+ tmpl = replaceYield(tmpl, html);
+
+ /* istanbul ignore next */
+ if (tblTags.test(tagName))
+ { el = specialTags(el, tmpl, tagName); }
+ else
+ { setInnerHTML(el, tmpl, isSvg); }
+
+ return el
+ }
+
+ var EVENT_ATTR_RE = /^on/;
+
+ /**
+ * True if the event attribute starts with 'on'
+ * @param { String } attribute - event attribute
+ * @returns { Boolean }
+ */
+ function isEventAttribute(attribute) {
+ return EVENT_ATTR_RE.test(attribute)
+ }
+
+ /**
+ * Loop backward all the parents tree to detect the first custom parent tag
+ * @param { Object } tag - a Tag instance
+ * @returns { Object } the instance of the first custom parent tag found
+ */
+ function getImmediateCustomParent(tag) {
+ var ptag = tag;
+ while (ptag.__.isAnonymous) {
+ if (!ptag.parent) { break }
+ ptag = ptag.parent;
+ }
+ return ptag
+ }
+
+ /**
+ * Trigger DOM events
+ * @param { HTMLElement } dom - dom element target of the event
+ * @param { Function } handler - user function
+ * @param { Object } e - event object
+ */
+ function handleEvent(dom, handler, e) {
+ var ptag = this.__.parent;
+ var item = this.__.item;
+
+ if (!item)
+ { while (ptag && !item) {
+ item = ptag.__.item;
+ ptag = ptag.__.parent;
+ } }
+
+ // override the event properties
+ /* istanbul ignore next */
+ if (isWritable(e, 'currentTarget')) { e.currentTarget = dom; }
+ /* istanbul ignore next */
+ if (isWritable(e, 'target')) { e.target = e.srcElement; }
+ /* istanbul ignore next */
+ if (isWritable(e, 'which')) { e.which = e.charCode || e.keyCode; }
+
+ e.item = item;
+
+ handler.call(this, e);
+
+ // avoid auto updates
+ if (!settings.autoUpdate) { return }
+
+ if (!e.preventUpdate) {
+ var p = getImmediateCustomParent(this);
+ // fixes #2083
+ if (p.isMounted) { p.update(); }
+ }
+ }
+
+ /**
+ * Attach an event to a DOM node
+ * @param { String } name - event name
+ * @param { Function } handler - event callback
+ * @param { Object } dom - dom node
+ * @param { Tag } tag - tag instance
+ */
+ function setEventHandler(name, handler, dom, tag) {
+ var eventName;
+ var cb = handleEvent.bind(tag, dom, handler);
+
+ // avoid to bind twice the same event
+ // possible fix for #2332
+ dom[name] = null;
+
+ // normalize event name
+ eventName = name.replace(RE_EVENTS_PREFIX, '');
+
+ // cache the listener into the listeners array
+ if (!contains(tag.__.listeners, dom)) { tag.__.listeners.push(dom); }
+ if (!dom[RIOT_EVENTS_KEY]) { dom[RIOT_EVENTS_KEY] = {}; }
+ if (dom[RIOT_EVENTS_KEY][name]) { dom.removeEventListener(eventName, dom[RIOT_EVENTS_KEY][name]); }
+
+ dom[RIOT_EVENTS_KEY][name] = cb;
+ dom.addEventListener(eventName, cb, false);
+ }
+
+ /**
+ * Create a new child tag including it correctly into its parent
+ * @param { Object } child - child tag implementation
+ * @param { Object } opts - tag options containing the DOM node where the tag will be mounted
+ * @param { String } innerHTML - inner html of the child node
+ * @param { Object } parent - instance of the parent tag including the child custom tag
+ * @returns { Object } instance of the new child tag just created
+ */
+ function initChild(child, opts, innerHTML, parent) {
+ var tag = createTag(child, opts, innerHTML);
+ var tagName = opts.tagName || getName(opts.root, true);
+ var ptag = getImmediateCustomParent(parent);
+ // fix for the parent attribute in the looped elements
+ define(tag, 'parent', ptag);
+ // store the real parent tag
+ // in some cases this could be different from the custom parent tag
+ // for example in nested loops
+ tag.__.parent = parent;
+
+ // add this tag to the custom parent tag
+ arrayishAdd(ptag.tags, tagName, tag);
+
+ // and also to the real parent tag
+ if (ptag !== parent)
+ { arrayishAdd(parent.tags, tagName, tag); }
+
+ return tag
+ }
+
+ /**
+ * Removes an item from an object at a given key. If the key points to an array,
+ * then the item is just removed from the array.
+ * @param { Object } obj - object on which to remove the property
+ * @param { String } key - property name
+ * @param { Object } value - the value of the property to be removed
+ * @param { Boolean } ensureArray - ensure that the property remains an array
+ */
+ function arrayishRemove(obj, key, value, ensureArray) {
+ if (isArray(obj[key])) {
+ var index = obj[key].indexOf(value);
+ if (index !== -1) { obj[key].splice(index, 1); }
+ if (!obj[key].length) { delete obj[key]; }
+ else if (obj[key].length === 1 && !ensureArray) { obj[key] = obj[key][0]; }
+ } else if (obj[key] === value)
+ { delete obj[key]; } // otherwise just delete the key
+ }
+
+ /**
+ * Adds the elements for a virtual tag
+ * @this Tag
+ * @param { Node } src - the node that will do the inserting or appending
+ * @param { Tag } target - only if inserting, insert before this tag's first child
+ */
+ function makeVirtual(src, target) {
+ var this$1 = this;
+
+ var head = createDOMPlaceholder();
+ var tail = createDOMPlaceholder();
+ var frag = createFragment();
+ var sib;
+ var el;
+
+ this.root.insertBefore(head, this.root.firstChild);
+ this.root.appendChild(tail);
+
+ this.__.head = el = head;
+ this.__.tail = tail;
+
+ while (el) {
+ sib = el.nextSibling;
+ frag.appendChild(el);
+ this$1.__.virts.push(el); // hold for unmounting
+ el = sib;
+ }
+
+ if (target)
+ { src.insertBefore(frag, target.__.head); }
+ else
+ { src.appendChild(frag); }
+ }
+
+ /**
+ * makes a tag virtual and replaces a reference in the dom
+ * @this Tag
+ * @param { tag } the tag to make virtual
+ * @param { ref } the dom reference location
+ */
+ function makeReplaceVirtual(tag, ref) {
+ if (!ref.parentNode) { return }
+ var frag = createFragment();
+ makeVirtual.call(tag, frag);
+ ref.parentNode.replaceChild(frag, ref);
+ }
+
+ /**
+ * Update dynamically created data-is tags with changing expressions
+ * @param { Object } expr - expression tag and expression info
+ * @param { Tag } parent - parent for tag creation
+ * @param { String } tagName - tag implementation we want to use
+ */
+ function updateDataIs(expr, parent, tagName) {
+ var tag = expr.tag || expr.dom._tag;
+ var ref;
+
+ var ref$1 = tag ? tag.__ : {};
+ var head = ref$1.head;
+ var isVirtual = expr.dom.tagName === 'VIRTUAL';
+
+ if (tag && expr.tagName === tagName) {
+ tag.update();
+ return
+ }
+
+ // sync _parent to accommodate changing tagnames
+ if (tag) {
+ // need placeholder before unmount
+ if(isVirtual) {
+ ref = createDOMPlaceholder();
+ head.parentNode.insertBefore(ref, head);
+ }
+
+ tag.unmount(true);
+ }
+
+ // unable to get the tag name
+ if (!isString(tagName)) { return }
+
+ expr.impl = __TAG_IMPL[tagName];
+
+ // unknown implementation
+ if (!expr.impl) { return }
+
+ expr.tag = tag = initChild(
+ expr.impl, {
+ root: expr.dom,
+ parent: parent,
+ tagName: tagName
+ },
+ expr.dom.innerHTML,
+ parent
+ );
+
+ each(expr.attrs, function (a) { return setAttribute(tag.root, a.name, a.value); });
+ expr.tagName = tagName;
+ tag.mount();
+
+ // root exist first time, after use placeholder
+ if (isVirtual) { makeReplaceVirtual(tag, ref || tag.root); }
+
+ // parent is the placeholder tag, not the dynamic tag so clean up
+ parent.__.onUnmount = function () {
+ var delName = tag.opts.dataIs;
+ arrayishRemove(tag.parent.tags, delName, tag);
+ arrayishRemove(tag.__.parent.tags, delName, tag);
+ tag.unmount();
+ };
+ }
+
+ /**
+ * Nomalize any attribute removing the "riot-" prefix
+ * @param { String } attrName - original attribute name
+ * @returns { String } valid html attribute name
+ */
+ function normalizeAttrName(attrName) {
+ if (!attrName) { return null }
+ attrName = attrName.replace(ATTRS_PREFIX, '');
+ if (CASE_SENSITIVE_ATTRIBUTES[attrName]) { attrName = CASE_SENSITIVE_ATTRIBUTES[attrName]; }
+ return attrName
+ }
+
+ /**
+ * Update on single tag expression
+ * @this Tag
+ * @param { Object } expr - expression logic
+ * @returns { undefined }
+ */
+ function updateExpression(expr) {
+ if (this.root && getAttribute(this.root,'virtualized')) { return }
+
+ var dom = expr.dom;
+ // remove the riot- prefix
+ var attrName = normalizeAttrName(expr.attr);
+ var isToggle = contains([SHOW_DIRECTIVE, HIDE_DIRECTIVE], attrName);
+ var isVirtual = expr.root && expr.root.tagName === 'VIRTUAL';
+ var ref = this.__;
+ var isAnonymous = ref.isAnonymous;
+ var parent = dom && (expr.parent || dom.parentNode);
+ var keepValueAttributes = settings.keepValueAttributes;
+ // detect the style attributes
+ var isStyleAttr = attrName === 'style';
+ var isClassAttr = attrName === 'class';
+ var isValueAttr = attrName === 'value';
+
+ var value;
+
+ // if it's a tag we could totally skip the rest
+ if (expr._riot_id) {
+ if (expr.__.wasCreated) {
+ expr.update();
+ // if it hasn't been mounted yet, do that now.
+ } else {
+ expr.mount();
+ if (isVirtual) {
+ makeReplaceVirtual(expr, expr.root);
+ }
+ }
+ return
+ }
+
+ // if this expression has the update method it means it can handle the DOM changes by itself
+ if (expr.update) { return expr.update() }
+
+ var context = isToggle && !isAnonymous ? inheritParentProps.call(this) : this;
+
+ // ...it seems to be a simple expression so we try to calculate its value
+ value = tmpl(expr.expr, context);
+
+ var hasValue = !isBlank(value);
+ var isObj = isObject(value);
+
+ // convert the style/class objects to strings
+ if (isObj) {
+ if (isClassAttr) {
+ value = tmpl(JSON.stringify(value), this);
+ } else if (isStyleAttr) {
+ value = styleObjectToString(value);
+ }
+ }
+
+ // remove original attribute
+ if (expr.attr &&
+ (
+ // the original attribute can be removed only if we are parsing the original expression
+ !expr.wasParsedOnce ||
+ // or its value is false
+ value === false ||
+ // or if its value is currently falsy...
+ // We will keep the "value" attributes if the "keepValueAttributes"
+ // is enabled though
+ (!hasValue && (!isValueAttr || isValueAttr && !keepValueAttributes))
+ )
+ ) {
+ // remove either riot-* attributes or just the attribute name
+ removeAttribute(dom, getAttribute(dom, expr.attr) ? expr.attr : attrName);
+ }
+
+ // for the boolean attributes we don't need the value
+ // we can convert it to checked=true to checked=checked
+ if (expr.bool) { value = value ? attrName : false; }
+ if (expr.isRtag) { return updateDataIs(expr, this, value) }
+ if (expr.wasParsedOnce && expr.value === value) { return }
+
+ // update the expression value
+ expr.value = value;
+ expr.wasParsedOnce = true;
+
+ // if the value is an object (and it's not a style or class attribute) we can not do much more with it
+ if (isObj && !isClassAttr && !isStyleAttr && !isToggle) { return }
+ // avoid to render undefined/null values
+ if (!hasValue) { value = ''; }
+
+ // textarea and text nodes have no attribute name
+ if (!attrName) {
+ // about #815 w/o replace: the browser converts the value to a string,
+ // the comparison by "==" does too, but not in the server
+ value += '';
+ // test for parent avoids error with invalid assignment to nodeValue
+ if (parent) {
+ // cache the parent node because somehow it will become null on IE
+ // on the next iteration
+ expr.parent = parent;
+ if (parent.tagName === 'TEXTAREA') {
+ parent.value = value; // #1113
+ if (!IE_VERSION) { dom.nodeValue = value; } // #1625 IE throws here, nodeValue
+ } // will be available on 'updated'
+ else { dom.nodeValue = value; }
+ }
+ return
+ }
+
+ switch (true) {
+ // handle events binding
+ case isFunction(value):
+ if (isEventAttribute(attrName)) {
+ setEventHandler(attrName, value, dom, this);
+ }
+ break
+ // show / hide
+ case isToggle:
+ toggleVisibility(dom, attrName === HIDE_DIRECTIVE ? !value : value);
+ break
+ // handle attributes
+ default:
+ if (expr.bool) {
+ dom[attrName] = value;
+ }
+
+ if (isValueAttr && dom.value !== value) {
+ dom.value = value;
+ } else if (hasValue && value !== false) {
+ setAttribute(dom, attrName, value);
+ }
+
+ // make sure that in case of style changes
+ // the element stays hidden
+ if (isStyleAttr && dom.hidden) { toggleVisibility(dom, false); }
+ }
+ }
+
+ /**
+ * Update all the expressions in a Tag instance
+ * @this Tag
+ * @param { Array } expressions - expression that must be re evaluated
+ */
+ function update(expressions) {
+ each(expressions, updateExpression.bind(this));
+ }
+
+ /**
+ * We need to update opts for this tag. That requires updating the expressions
+ * in any attributes on the tag, and then copying the result onto opts.
+ * @this Tag
+ * @param {Boolean} isLoop - is it a loop tag?
+ * @param { Tag } parent - parent tag node
+ * @param { Boolean } isAnonymous - is it a tag without any impl? (a tag not registered)
+ * @param { Object } opts - tag options
+ * @param { Array } instAttrs - tag attributes array
+ */
+ function updateOpts(isLoop, parent, isAnonymous, opts, instAttrs) {
+ // isAnonymous `each` tags treat `dom` and `root` differently. In this case
+ // (and only this case) we don't need to do updateOpts, because the regular parse
+ // will update those attrs. Plus, isAnonymous tags don't need opts anyway
+ if (isLoop && isAnonymous) { return }
+ var ctx = isLoop ? inheritParentProps.call(this) : parent || this;
+
+ each(instAttrs, function (attr) {
+ if (attr.expr) { updateExpression.call(ctx, attr.expr); }
+ // normalize the attribute names
+ opts[toCamel(attr.name).replace(ATTRS_PREFIX, '')] = attr.expr ? attr.expr.value : attr.value;
+ });
+ }
+
+ /**
+ * Update the tag expressions and options
+ * @param { Tag } tag - tag object
+ * @param { * } data - data we want to use to extend the tag properties
+ * @param { Array } expressions - component expressions array
+ * @returns { Tag } the current tag instance
+ */
+ function componentUpdate(tag, data, expressions) {
+ var __ = tag.__;
+ var nextOpts = {};
+ var canTrigger = tag.isMounted && !__.skipAnonymous;
+
+ // inherit properties from the parent tag
+ if (__.isAnonymous && __.parent) { extend(tag, __.parent); }
+ extend(tag, data);
+
+ updateOpts.apply(tag, [__.isLoop, __.parent, __.isAnonymous, nextOpts, __.instAttrs]);
+
+ if (
+ canTrigger &&
+ tag.isMounted &&
+ isFunction(tag.shouldUpdate) && !tag.shouldUpdate(data, nextOpts)
+ ) {
+ return tag
+ }
+
+ extend(tag.opts, nextOpts);
+
+ if (canTrigger) { tag.trigger('update', data); }
+ update.call(tag, expressions);
+ if (canTrigger) { tag.trigger('updated'); }
+
+ return tag
+ }
+
+ /**
+ * Get selectors for tags
+ * @param { Array } tags - tag names to select
+ * @returns { String } selector
+ */
+ function query(tags) {
+ // select all tags
+ if (!tags) {
+ var keys = Object.keys(__TAG_IMPL);
+ return keys + query(keys)
+ }
+
+ return tags
+ .filter(function (t) { return !/[^-\w]/.test(t); })
+ .reduce(function (list, t) {
+ var name = t.trim().toLowerCase();
+ return list + ",[" + IS_DIRECTIVE + "=\"" + name + "\"]"
+ }, '')
+ }
+
+ /**
+ * Another way to create a riot tag a bit more es6 friendly
+ * @param { HTMLElement } el - tag DOM selector or DOM node/s
+ * @param { Object } opts - tag logic
+ * @returns { Tag } new riot tag instance
+ */
+ function Tag(el, opts) {
+ // get the tag properties from the class constructor
+ var ref = this;
+ var name = ref.name;
+ var tmpl = ref.tmpl;
+ var css = ref.css;
+ var attrs = ref.attrs;
+ var onCreate = ref.onCreate;
+ // register a new tag and cache the class prototype
+ if (!__TAG_IMPL[name]) {
+ tag(name, tmpl, css, attrs, onCreate);
+ // cache the class constructor
+ __TAG_IMPL[name].class = this.constructor;
+ }
+
+ // mount the tag using the class instance
+ mount$1(el, name, opts, this);
+ // inject the component css
+ if (css) { styleManager.inject(); }
+
+ return this
+ }
+
+ /**
+ * Create a new riot tag implementation
+ * @param { String } name - name/id of the new riot tag
+ * @param { String } tmpl - tag template
+ * @param { String } css - custom tag css
+ * @param { String } attrs - root tag attributes
+ * @param { Function } fn - user function
+ * @returns { String } name/id of the tag just created
+ */
+ function tag(name, tmpl, css, attrs, fn) {
+ if (isFunction(attrs)) {
+ fn = attrs;
+
+ if (/^[\w-]+\s?=/.test(css)) {
+ attrs = css;
+ css = '';
+ } else
+ { attrs = ''; }
+ }
+
+ if (css) {
+ if (isFunction(css))
+ { fn = css; }
+ else
+ { styleManager.add(css, name); }
+ }
+
+ name = name.toLowerCase();
+ __TAG_IMPL[name] = { name: name, tmpl: tmpl, attrs: attrs, fn: fn };
+
+ return name
+ }
+
+ /**
+ * Create a new riot tag implementation (for use by the compiler)
+ * @param { String } name - name/id of the new riot tag
+ * @param { String } tmpl - tag template
+ * @param { String } css - custom tag css
+ * @param { String } attrs - root tag attributes
+ * @param { Function } fn - user function
+ * @returns { String } name/id of the tag just created
+ */
+ function tag2(name, tmpl, css, attrs, fn) {
+ if (css) { styleManager.add(css, name); }
+
+ __TAG_IMPL[name] = { name: name, tmpl: tmpl, attrs: attrs, fn: fn };
+
+ return name
+ }
+
+ /**
+ * Mount a tag using a specific tag implementation
+ * @param { * } selector - tag DOM selector or DOM node/s
+ * @param { String } tagName - tag implementation name
+ * @param { Object } opts - tag logic
+ * @returns { Array } new tags instances
+ */
+ function mount(selector, tagName, opts) {
+ var tags = [];
+ var elem, allTags;
+
+ function pushTagsTo(root) {
+ if (root.tagName) {
+ var riotTag = getAttribute(root, IS_DIRECTIVE), tag;
+
+ // have tagName? force riot-tag to be the same
+ if (tagName && riotTag !== tagName) {
+ riotTag = tagName;
+ setAttribute(root, IS_DIRECTIVE, tagName);
+ }
+
+ tag = mount$1(
+ root,
+ riotTag || root.tagName.toLowerCase(),
+ isFunction(opts) ? opts() : opts
+ );
+
+ if (tag)
+ { tags.push(tag); }
+ } else if (root.length)
+ { each(root, pushTagsTo); } // assume nodeList
+ }
+
+ // inject styles into DOM
+ styleManager.inject();
+
+ if (isObject(tagName) || isFunction(tagName)) {
+ opts = tagName;
+ tagName = 0;
+ }
+
+ // crawl the DOM to find the tag
+ if (isString(selector)) {
+ selector = selector === '*' ?
+ // select all registered tags
+ // & tags found with the riot-tag attribute set
+ allTags = query() :
+ // or just the ones named like the selector
+ selector + query(selector.split(/, */));
+
+ // make sure to pass always a selector
+ // to the querySelectorAll function
+ elem = selector ? $$(selector) : [];
+ }
+ else
+ // probably you have passed already a tag or a NodeList
+ { elem = selector; }
+
+ // select all the registered and mount them inside their root elements
+ if (tagName === '*') {
+ // get all custom tags
+ tagName = allTags || query();
+ // if the root els it's just a single tag
+ if (elem.tagName)
+ { elem = $$(tagName, elem); }
+ else {
+ // select all the children for all the different root elements
+ var nodeList = [];
+
+ each(elem, function (_el) { return nodeList.push($$(tagName, _el)); });
+
+ elem = nodeList;
+ }
+ // get rid of the tagName
+ tagName = 0;
+ }
+
+ pushTagsTo(elem);
+
+ return tags
+ }
+
+ // Create a mixin that could be globally shared across all the tags
+ var mixins = {};
+ var globals = mixins[GLOBAL_MIXIN] = {};
+ var mixins_id = 0;
+
+ /**
+ * Create/Return a mixin by its name
+ * @param { String } name - mixin name (global mixin if object)
+ * @param { Object } mix - mixin logic
+ * @param { Boolean } g - is global?
+ * @returns { Object } the mixin logic
+ */
+ function mixin(name, mix, g) {
+ // Unnamed global
+ if (isObject(name)) {
+ mixin(("__" + (mixins_id++) + "__"), name, true);
+ return
+ }
+
+ var store = g ? globals : mixins;
+
+ // Getter
+ if (!mix) {
+ if (isUndefined(store[name]))
+ { throw new Error(("Unregistered mixin: " + name)) }
+
+ return store[name]
+ }
+
+ // Setter
+ store[name] = isFunction(mix) ?
+ extend(mix.prototype, store[name] || {}) && mix :
+ extend(store[name] || {}, mix);
+ }
+
+ /**
+ * Update all the tags instances created
+ * @returns { Array } all the tags instances
+ */
+ function update$1() {
+ return each(__TAGS_CACHE, function (tag) { return tag.update(); })
+ }
+
+ function unregister(name) {
+ styleManager.remove(name);
+ return delete __TAG_IMPL[name]
+ }
+
+ var version = 'v3.13.2';
+
+ var core = /*#__PURE__*/Object.freeze({
+ Tag: Tag,
+ tag: tag,
+ tag2: tag2,
+ mount: mount,
+ mixin: mixin,
+ update: update$1,
+ unregister: unregister,
+ version: version
+ });
+
+ /**
+ * Add a mixin to this tag
+ * @returns { Tag } the current tag instance
+ */
+ function componentMixin(tag$$1) {
+ var mixins = [], len = arguments.length - 1;
+ while ( len-- > 0 ) mixins[ len ] = arguments[ len + 1 ];
+
+ each(mixins, function (mix) {
+ var instance;
+ var obj;
+ var props = [];
+
+ // properties blacklisted and will not be bound to the tag instance
+ var propsBlacklist = ['init', '__proto__'];
+
+ mix = isString(mix) ? mixin(mix) : mix;
+
+ // check if the mixin is a function
+ if (isFunction(mix)) {
+ // create the new mixin instance
+ instance = new mix();
+ } else { instance = mix; }
+
+ var proto = Object.getPrototypeOf(instance);
+
+ // build multilevel prototype inheritance chain property list
+ do { props = props.concat(Object.getOwnPropertyNames(obj || instance)); }
+ while (obj = Object.getPrototypeOf(obj || instance))
+
+ // loop the keys in the function prototype or the all object keys
+ each(props, function (key) {
+ // bind methods to tag
+ // allow mixins to override other properties/parent mixins
+ if (!contains(propsBlacklist, key)) {
+ // check for getters/setters
+ var descriptor = getPropDescriptor(instance, key) || getPropDescriptor(proto, key);
+ var hasGetterSetter = descriptor && (descriptor.get || descriptor.set);
+
+ // apply method only if it does not already exist on the instance
+ if (!tag$$1.hasOwnProperty(key) && hasGetterSetter) {
+ Object.defineProperty(tag$$1, key, descriptor);
+ } else {
+ tag$$1[key] = isFunction(instance[key]) ?
+ instance[key].bind(tag$$1) :
+ instance[key];
+ }
+ }
+ });
+
+ // init method will be called automatically
+ if (instance.init)
+ { instance.init.bind(tag$$1)(tag$$1.opts); }
+ });
+
+ return tag$$1
+ }
+
+ /**
+ * Move the position of a custom tag in its parent tag
+ * @this Tag
+ * @param { String } tagName - key where the tag was stored
+ * @param { Number } newPos - index where the new tag will be stored
+ */
+ function moveChild(tagName, newPos) {
+ var parent = this.parent;
+ var tags;
+ // no parent no move
+ if (!parent) { return }
+
+ tags = parent.tags[tagName];
+
+ if (isArray(tags))
+ { tags.splice(newPos, 0, tags.splice(tags.indexOf(this), 1)[0]); }
+ else { arrayishAdd(parent.tags, tagName, this); }
+ }
+
+ /**
+ * Move virtual tag and all child nodes
+ * @this Tag
+ * @param { Node } src - the node that will do the inserting
+ * @param { Tag } target - insert before this tag's first child
+ */
+ function moveVirtual(src, target) {
+ var this$1 = this;
+
+ var el = this.__.head;
+ var sib;
+ var frag = createFragment();
+
+ while (el) {
+ sib = el.nextSibling;
+ frag.appendChild(el);
+ el = sib;
+ if (el === this$1.__.tail) {
+ frag.appendChild(el);
+ src.insertBefore(frag, target.__.head);
+ break
+ }
+ }
+ }
+
+ /**
+ * Convert the item looped into an object used to extend the child tag properties
+ * @param { Object } expr - object containing the keys used to extend the children tags
+ * @param { * } key - value to assign to the new object returned
+ * @param { * } val - value containing the position of the item in the array
+ * @returns { Object } - new object containing the values of the original item
+ *
+ * The variables 'key' and 'val' are arbitrary.
+ * They depend on the collection type looped (Array, Object)
+ * and on the expression used on the each tag
+ *
+ */
+ function mkitem(expr, key, val) {
+ var item = {};
+ item[expr.key] = key;
+ if (expr.pos) { item[expr.pos] = val; }
+ return item
+ }
+
+ /**
+ * Unmount the redundant tags
+ * @param { Array } items - array containing the current items to loop
+ * @param { Array } tags - array containing all the children tags
+ */
+ function unmountRedundant(items, tags, filteredItemsCount) {
+ var i = tags.length;
+ var j = items.length - filteredItemsCount;
+
+ while (i > j) {
+ i--;
+ remove.apply(tags[i], [tags, i]);
+ }
+ }
+
+
+ /**
+ * Remove a child tag
+ * @this Tag
+ * @param { Array } tags - tags collection
+ * @param { Number } i - index of the tag to remove
+ */
+ function remove(tags, i) {
+ tags.splice(i, 1);
+ this.unmount();
+ arrayishRemove(this.parent, this, this.__.tagName, true);
+ }
+
+ /**
+ * Move the nested custom tags in non custom loop tags
+ * @this Tag
+ * @param { Number } i - current position of the loop tag
+ */
+ function moveNestedTags(i) {
+ var this$1 = this;
+
+ each(Object.keys(this.tags), function (tagName) {
+ moveChild.apply(this$1.tags[tagName], [tagName, i]);
+ });
+ }
+
+ /**
+ * Move a child tag
+ * @this Tag
+ * @param { HTMLElement } root - dom node containing all the loop children
+ * @param { Tag } nextTag - instance of the next tag preceding the one we want to move
+ * @param { Boolean } isVirtual - is it a virtual tag?
+ */
+ function move(root, nextTag, isVirtual) {
+ if (isVirtual)
+ { moveVirtual.apply(this, [root, nextTag]); }
+ else
+ { safeInsert(root, this.root, nextTag.root); }
+ }
+
+ /**
+ * Insert and mount a child tag
+ * @this Tag
+ * @param { HTMLElement } root - dom node containing all the loop children
+ * @param { Tag } nextTag - instance of the next tag preceding the one we want to insert
+ * @param { Boolean } isVirtual - is it a virtual tag?
+ */
+ function insert(root, nextTag, isVirtual) {
+ if (isVirtual)
+ { makeVirtual.apply(this, [root, nextTag]); }
+ else
+ { safeInsert(root, this.root, nextTag.root); }
+ }
+
+ /**
+ * Append a new tag into the DOM
+ * @this Tag
+ * @param { HTMLElement } root - dom node containing all the loop children
+ * @param { Boolean } isVirtual - is it a virtual tag?
+ */
+ function append(root, isVirtual) {
+ if (isVirtual)
+ { makeVirtual.call(this, root); }
+ else
+ { root.appendChild(this.root); }
+ }
+
+ /**
+ * Return the value we want to use to lookup the postion of our items in the collection
+ * @param { String } keyAttr - lookup string or expression
+ * @param { * } originalItem - original item from the collection
+ * @param { Object } keyedItem - object created by riot via { item, i in collection }
+ * @param { Boolean } hasKeyAttrExpr - flag to check whether the key is an expression
+ * @returns { * } value that we will use to figure out the item position via collection.indexOf
+ */
+ function getItemId(keyAttr, originalItem, keyedItem, hasKeyAttrExpr) {
+ if (keyAttr) {
+ return hasKeyAttrExpr ? tmpl(keyAttr, keyedItem) : originalItem[keyAttr]
+ }
+
+ return originalItem
+ }
+
+ /**
+ * Manage tags having the 'each'
+ * @param { HTMLElement } dom - DOM node we need to loop
+ * @param { Tag } parent - parent tag instance where the dom node is contained
+ * @param { String } expr - string contained in the 'each' attribute
+ * @returns { Object } expression object for this each loop
+ */
+ function _each(dom, parent, expr) {
+ var mustReorder = typeof getAttribute(dom, LOOP_NO_REORDER_DIRECTIVE) !== T_STRING || removeAttribute(dom, LOOP_NO_REORDER_DIRECTIVE);
+ var keyAttr = getAttribute(dom, KEY_DIRECTIVE);
+ var hasKeyAttrExpr = keyAttr ? tmpl.hasExpr(keyAttr) : false;
+ var tagName = getName(dom);
+ var impl = __TAG_IMPL[tagName];
+ var parentNode = dom.parentNode;
+ var placeholder = createDOMPlaceholder();
+ var child = get(dom);
+ var ifExpr = getAttribute(dom, CONDITIONAL_DIRECTIVE);
+ var tags = [];
+ var isLoop = true;
+ var innerHTML = dom.innerHTML;
+ var isAnonymous = !__TAG_IMPL[tagName];
+ var isVirtual = dom.tagName === 'VIRTUAL';
+ var oldItems = [];
+
+ // remove the each property from the original tag
+ removeAttribute(dom, LOOP_DIRECTIVE);
+ removeAttribute(dom, KEY_DIRECTIVE);
+
+ // parse the each expression
+ expr = tmpl.loopKeys(expr);
+ expr.isLoop = true;
+
+ if (ifExpr) { removeAttribute(dom, CONDITIONAL_DIRECTIVE); }
+
+ // insert a marked where the loop tags will be injected
+ parentNode.insertBefore(placeholder, dom);
+ parentNode.removeChild(dom);
+
+ expr.update = function updateEach() {
+ // get the new items collection
+ expr.value = tmpl(expr.val, parent);
+
+ var items = expr.value;
+ var frag = createFragment();
+ var isObject = !isArray(items) && !isString(items);
+ var root = placeholder.parentNode;
+ var tmpItems = [];
+ var hasKeys = isObject && !!items;
+
+ // if this DOM was removed the update here is useless
+ // this condition fixes also a weird async issue on IE in our unit test
+ if (!root) { return }
+
+ // object loop. any changes cause full redraw
+ if (isObject) {
+ items = items ? Object.keys(items).map(function (key) { return mkitem(expr, items[key], key); }) : [];
+ }
+
+ // store the amount of filtered items
+ var filteredItemsCount = 0;
+
+ // loop all the new items
+ each(items, function (_item, index) {
+ var i = index - filteredItemsCount;
+ var item = !hasKeys && expr.key ? mkitem(expr, _item, index) : _item;
+
+ // skip this item because it must be filtered
+ if (ifExpr && !tmpl(ifExpr, extend(create(parent), item))) {
+ filteredItemsCount ++;
+ return
+ }
+
+ var itemId = getItemId(keyAttr, _item, item, hasKeyAttrExpr);
+ // reorder only if the items are not objects
+ // or a key attribute has been provided
+ var doReorder = !isObject && mustReorder && typeof _item === T_OBJECT || keyAttr;
+ var oldPos = oldItems.indexOf(itemId);
+ var isNew = oldPos === -1;
+ var pos = !isNew && doReorder ? oldPos : i;
+ // does a tag exist in this position?
+ var tag = tags[pos];
+ var mustAppend = i >= oldItems.length;
+ var mustCreate = doReorder && isNew || !doReorder && !tag || !tags[i];
+
+ // new tag
+ if (mustCreate) {
+ tag = createTag(impl, {
+ parent: parent,
+ isLoop: isLoop,
+ isAnonymous: isAnonymous,
+ tagName: tagName,
+ root: dom.cloneNode(isAnonymous),
+ item: item,
+ index: i,
+ }, innerHTML);
+
+ // mount the tag
+ tag.mount();
+
+ if (mustAppend)
+ { append.apply(tag, [frag || root, isVirtual]); }
+ else
+ { insert.apply(tag, [root, tags[i], isVirtual]); }
+
+ if (!mustAppend) { oldItems.splice(i, 0, item); }
+ tags.splice(i, 0, tag);
+ if (child) { arrayishAdd(parent.tags, tagName, tag, true); }
+ } else if (pos !== i && doReorder) {
+ // move
+ if (keyAttr || contains(items, oldItems[pos])) {
+ move.apply(tag, [root, tags[i], isVirtual]);
+ // move the old tag instance
+ tags.splice(i, 0, tags.splice(pos, 1)[0]);
+ // move the old item
+ oldItems.splice(i, 0, oldItems.splice(pos, 1)[0]);
+ }
+
+ // update the position attribute if it exists
+ if (expr.pos) { tag[expr.pos] = i; }
+
+ // if the loop tags are not custom
+ // we need to move all their custom tags into the right position
+ if (!child && tag.tags) { moveNestedTags.call(tag, i); }
+ }
+
+ // cache the original item to use it in the events bound to this node
+ // and its children
+ extend(tag.__, {
+ item: item,
+ index: i,
+ parent: parent
+ });
+
+ tmpItems[i] = itemId;
+
+ if (!mustCreate) { tag.update(item); }
+ });
+
+ // remove the redundant tags
+ unmountRedundant(items, tags, filteredItemsCount);
+
+ // clone the items array
+ oldItems = tmpItems.slice();
+
+ root.insertBefore(frag, placeholder);
+ };
+
+ expr.unmount = function () {
+ each(tags, function (t) { t.unmount(); });
+ };
+
+ return expr
+ }
+
+ var RefExpr = {
+ init: function init(dom, parent, attrName, attrValue) {
+ this.dom = dom;
+ this.attr = attrName;
+ this.rawValue = attrValue;
+ this.parent = parent;
+ this.hasExp = tmpl.hasExpr(attrValue);
+ return this
+ },
+ update: function update() {
+ var old = this.value;
+ var customParent = this.parent && getImmediateCustomParent(this.parent);
+ // if the referenced element is a custom tag, then we set the tag itself, rather than DOM
+ var tagOrDom = this.dom.__ref || this.tag || this.dom;
+
+ this.value = this.hasExp ? tmpl(this.rawValue, this.parent) : this.rawValue;
+
+ // the name changed, so we need to remove it from the old key (if present)
+ if (!isBlank(old) && customParent) { arrayishRemove(customParent.refs, old, tagOrDom); }
+ if (!isBlank(this.value) && isString(this.value)) {
+ // add it to the refs of parent tag (this behavior was changed >=3.0)
+ if (customParent) { arrayishAdd(
+ customParent.refs,
+ this.value,
+ tagOrDom,
+ // use an array if it's a looped node and the ref is not an expression
+ null,
+ this.parent.__.index
+ ); }
+
+ if (this.value !== old) {
+ setAttribute(this.dom, this.attr, this.value);
+ }
+ } else {
+ removeAttribute(this.dom, this.attr);
+ }
+
+ // cache the ref bound to this dom node
+ // to reuse it in future (see also #2329)
+ if (!this.dom.__ref) { this.dom.__ref = tagOrDom; }
+ },
+ unmount: function unmount() {
+ var tagOrDom = this.tag || this.dom;
+ var customParent = this.parent && getImmediateCustomParent(this.parent);
+ if (!isBlank(this.value) && customParent)
+ { arrayishRemove(customParent.refs, this.value, tagOrDom); }
+ }
+ };
+
+ /**
+ * Create a new ref directive
+ * @param { HTMLElement } dom - dom node having the ref attribute
+ * @param { Tag } context - tag instance where the DOM node is located
+ * @param { String } attrName - either 'ref' or 'data-ref'
+ * @param { String } attrValue - value of the ref attribute
+ * @returns { RefExpr } a new RefExpr object
+ */
+ function createRefDirective(dom, tag, attrName, attrValue) {
+ return create(RefExpr).init(dom, tag, attrName, attrValue)
+ }
+
+ /**
+ * Trigger the unmount method on all the expressions
+ * @param { Array } expressions - DOM expressions
+ */
+ function unmountAll(expressions) {
+ each(expressions, function (expr) {
+ if (expr.unmount) { expr.unmount(true); }
+ else if (expr.tagName) { expr.tag.unmount(true); }
+ else if (expr.unmount) { expr.unmount(); }
+ });
+ }
+
+ var IfExpr = {
+ init: function init(dom, tag, expr) {
+ removeAttribute(dom, CONDITIONAL_DIRECTIVE);
+ extend(this, { tag: tag, expr: expr, stub: createDOMPlaceholder(), pristine: dom });
+ var p = dom.parentNode;
+ p.insertBefore(this.stub, dom);
+ p.removeChild(dom);
+
+ return this
+ },
+ update: function update$$1() {
+ this.value = tmpl(this.expr, this.tag);
+
+ if (!this.stub.parentNode) { return }
+
+ if (this.value && !this.current) { // insert
+ this.current = this.pristine.cloneNode(true);
+ this.stub.parentNode.insertBefore(this.current, this.stub);
+ this.expressions = parseExpressions.apply(this.tag, [this.current, true]);
+ } else if (!this.value && this.current) { // remove
+ this.unmount();
+ this.current = null;
+ this.expressions = [];
+ }
+
+ if (this.value) { update.call(this.tag, this.expressions); }
+ },
+ unmount: function unmount() {
+ if (this.current) {
+ if (this.current._tag) {
+ this.current._tag.unmount();
+ } else if (this.current.parentNode) {
+ this.current.parentNode.removeChild(this.current);
+ }
+ }
+
+ unmountAll(this.expressions || []);
+ }
+ };
+
+ /**
+ * Create a new if directive
+ * @param { HTMLElement } dom - if root dom node
+ * @param { Tag } context - tag instance where the DOM node is located
+ * @param { String } attr - if expression
+ * @returns { IFExpr } a new IfExpr object
+ */
+ function createIfDirective(dom, tag, attr) {
+ return create(IfExpr).init(dom, tag, attr)
+ }
+
+ /**
+ * Walk the tag DOM to detect the expressions to evaluate
+ * @this Tag
+ * @param { HTMLElement } root - root tag where we will start digging the expressions
+ * @param { Boolean } mustIncludeRoot - flag to decide whether the root must be parsed as well
+ * @returns { Array } all the expressions found
+ */
+ function parseExpressions(root, mustIncludeRoot) {
+ var this$1 = this;
+
+ var expressions = [];
+
+ walkNodes(root, function (dom) {
+ var type = dom.nodeType;
+ var attr;
+ var tagImpl;
+
+ if (!mustIncludeRoot && dom === root) { return }
+
+ // text node
+ if (type === 3 && dom.parentNode.tagName !== 'STYLE' && tmpl.hasExpr(dom.nodeValue))
+ { expressions.push({dom: dom, expr: dom.nodeValue}); }
+
+ if (type !== 1) { return }
+
+ var isVirtual = dom.tagName === 'VIRTUAL';
+
+ // loop. each does it's own thing (for now)
+ if (attr = getAttribute(dom, LOOP_DIRECTIVE)) {
+ if(isVirtual) { setAttribute(dom, 'loopVirtual', true); } // ignore here, handled in _each
+ expressions.push(_each(dom, this$1, attr));
+ return false
+ }
+
+ // if-attrs become the new parent. Any following expressions (either on the current
+ // element, or below it) become children of this expression.
+ if (attr = getAttribute(dom, CONDITIONAL_DIRECTIVE)) {
+ expressions.push(createIfDirective(dom, this$1, attr));
+ return false
+ }
+
+ if (attr = getAttribute(dom, IS_DIRECTIVE)) {
+ if (tmpl.hasExpr(attr)) {
+ expressions.push({
+ isRtag: true,
+ expr: attr,
+ dom: dom,
+ attrs: [].slice.call(dom.attributes)
+ });
+
+ return false
+ }
+ }
+
+ // if this is a tag, stop traversing here.
+ // we ignore the root, since parseExpressions is called while we're mounting that root
+ tagImpl = get(dom);
+
+ if(isVirtual) {
+ if(getAttribute(dom, 'virtualized')) {dom.parentElement.removeChild(dom); } // tag created, remove from dom
+ if(!tagImpl && !getAttribute(dom, 'virtualized') && !getAttribute(dom, 'loopVirtual')) // ok to create virtual tag
+ { tagImpl = { tmpl: dom.outerHTML }; }
+ }
+
+ if (tagImpl && (dom !== root || mustIncludeRoot)) {
+ var hasIsDirective = getAttribute(dom, IS_DIRECTIVE);
+ if(isVirtual && !hasIsDirective) { // handled in update
+ // can not remove attribute like directives
+ // so flag for removal after creation to prevent maximum stack error
+ setAttribute(dom, 'virtualized', true);
+ var tag = createTag(
+ {tmpl: dom.outerHTML},
+ {root: dom, parent: this$1},
+ dom.innerHTML
+ );
+
+ expressions.push(tag); // no return, anonymous tag, keep parsing
+ } else {
+ if (hasIsDirective && isVirtual)
+ { warn(("Virtual tags shouldn't be used together with the \"" + IS_DIRECTIVE + "\" attribute - https://github.com/riot/riot/issues/2511")); }
+
+ expressions.push(
+ initChild(
+ tagImpl,
+ {
+ root: dom,
+ parent: this$1
+ },
+ dom.innerHTML,
+ this$1
+ )
+ );
+ return false
+ }
+ }
+
+ // attribute expressions
+ parseAttributes.apply(this$1, [dom, dom.attributes, function (attr, expr) {
+ if (!expr) { return }
+ expressions.push(expr);
+ }]);
+ });
+
+ return expressions
+ }
+
+ /**
+ * Calls `fn` for every attribute on an element. If that attr has an expression,
+ * it is also passed to fn.
+ * @this Tag
+ * @param { HTMLElement } dom - dom node to parse
+ * @param { Array } attrs - array of attributes
+ * @param { Function } fn - callback to exec on any iteration
+ */
+ function parseAttributes(dom, attrs, fn) {
+ var this$1 = this;
+
+ each(attrs, function (attr) {
+ if (!attr) { return false }
+
+ var name = attr.name;
+ var bool = isBoolAttr(name);
+ var expr;
+
+ if (contains(REF_DIRECTIVES, name) && dom.tagName.toLowerCase() !== YIELD_TAG) {
+ expr = createRefDirective(dom, this$1, name, attr.value);
+ } else if (tmpl.hasExpr(attr.value)) {
+ expr = {dom: dom, expr: attr.value, attr: name, bool: bool};
+ }
+
+ fn(attr, expr);
+ });
+ }
+
+ /**
+ * Manage the mount state of a tag triggering also the observable events
+ * @this Tag
+ * @param { Boolean } value - ..of the isMounted flag
+ */
+ function setMountState(value) {
+ var ref = this.__;
+ var isAnonymous = ref.isAnonymous;
+ var skipAnonymous = ref.skipAnonymous;
+
+ define(this, 'isMounted', value);
+
+ if (!isAnonymous || !skipAnonymous) {
+ if (value) { this.trigger('mount'); }
+ else {
+ this.trigger('unmount');
+ this.off('*');
+ this.__.wasCreated = false;
+ }
+ }
+ }
+
+ /**
+ * Mount the current tag instance
+ * @returns { Tag } the current tag instance
+ */
+ function componentMount(tag$$1, dom, expressions, opts) {
+ var __ = tag$$1.__;
+ var root = __.root;
+ root._tag = tag$$1; // keep a reference to the tag just created
+
+ // Read all the attrs on this instance. This give us the info we need for updateOpts
+ parseAttributes.apply(__.parent, [root, root.attributes, function (attr, expr) {
+ if (!__.isAnonymous && RefExpr.isPrototypeOf(expr)) { expr.tag = tag$$1; }
+ attr.expr = expr;
+ __.instAttrs.push(attr);
+ }]);
+
+ // update the root adding custom attributes coming from the compiler
+ walkAttributes(__.impl.attrs, function (k, v) { __.implAttrs.push({name: k, value: v}); });
+ parseAttributes.apply(tag$$1, [root, __.implAttrs, function (attr, expr) {
+ if (expr) { expressions.push(expr); }
+ else { setAttribute(root, attr.name, attr.value); }
+ }]);
+
+ // initialiation
+ updateOpts.apply(tag$$1, [__.isLoop, __.parent, __.isAnonymous, opts, __.instAttrs]);
+
+ // add global mixins
+ var globalMixin = mixin(GLOBAL_MIXIN);
+
+ if (globalMixin && !__.skipAnonymous) {
+ for (var i in globalMixin) {
+ if (globalMixin.hasOwnProperty(i)) {
+ tag$$1.mixin(globalMixin[i]);
+ }
+ }
+ }
+
+ if (__.impl.fn) { __.impl.fn.call(tag$$1, opts); }
+
+ if (!__.skipAnonymous) { tag$$1.trigger('before-mount'); }
+
+ // parse layout after init. fn may calculate args for nested custom tags
+ each(parseExpressions.apply(tag$$1, [dom, __.isAnonymous]), function (e) { return expressions.push(e); });
+
+ tag$$1.update(__.item);
+
+ if (!__.isAnonymous && !__.isInline) {
+ while (dom.firstChild) { root.appendChild(dom.firstChild); }
+ }
+
+ define(tag$$1, 'root', root);
+
+ // if we need to wait that the parent "mount" or "updated" event gets triggered
+ if (!__.skipAnonymous && tag$$1.parent) {
+ var p = getImmediateCustomParent(tag$$1.parent);
+ p.one(!p.isMounted ? 'mount' : 'updated', function () {
+ setMountState.call(tag$$1, true);
+ });
+ } else {
+ // otherwise it's not a child tag we can trigger its mount event
+ setMountState.call(tag$$1, true);
+ }
+
+ tag$$1.__.wasCreated = true;
+
+ return tag$$1
+ }
+
+ /**
+ * Unmount the tag instance
+ * @param { Boolean } mustKeepRoot - if it's true the root node will not be removed
+ * @returns { Tag } the current tag instance
+ */
+ function tagUnmount(tag, mustKeepRoot, expressions) {
+ var __ = tag.__;
+ var root = __.root;
+ var tagIndex = __TAGS_CACHE.indexOf(tag);
+ var p = root.parentNode;
+
+ if (!__.skipAnonymous) { tag.trigger('before-unmount'); }
+
+ // clear all attributes coming from the mounted tag
+ walkAttributes(__.impl.attrs, function (name) {
+ if (startsWith(name, ATTRS_PREFIX))
+ { name = name.slice(ATTRS_PREFIX.length); }
+
+ removeAttribute(root, name);
+ });
+
+ // remove all the event listeners
+ tag.__.listeners.forEach(function (dom) {
+ Object.keys(dom[RIOT_EVENTS_KEY]).forEach(function (eventName) {
+ dom.removeEventListener(eventName, dom[RIOT_EVENTS_KEY][eventName]);
+ });
+ });
+
+ // remove tag instance from the global tags cache collection
+ if (tagIndex !== -1) { __TAGS_CACHE.splice(tagIndex, 1); }
+
+ // clean up the parent tags object
+ if (__.parent && !__.isAnonymous) {
+ var ptag = getImmediateCustomParent(__.parent);
+
+ if (__.isVirtual) {
+ Object
+ .keys(tag.tags)
+ .forEach(function (tagName) { return arrayishRemove(ptag.tags, tagName, tag.tags[tagName]); });
+ } else {
+ arrayishRemove(ptag.tags, __.tagName, tag);
+ }
+ }
+
+ // unmount all the virtual directives
+ if (tag.__.virts) {
+ each(tag.__.virts, function (v) {
+ if (v.parentNode) { v.parentNode.removeChild(v); }
+ });
+ }
+
+ // allow expressions to unmount themselves
+ unmountAll(expressions);
+ each(__.instAttrs, function (a) { return a.expr && a.expr.unmount && a.expr.unmount(); });
+
+ // clear the tag html if it's necessary
+ if (mustKeepRoot) { setInnerHTML(root, ''); }
+ // otherwise detach the root tag from the DOM
+ else if (p) { p.removeChild(root); }
+
+ // custom internal unmount function to avoid relying on the observable
+ if (__.onUnmount) { __.onUnmount(); }
+
+ // weird fix for a weird edge case #2409 and #2436
+ // some users might use your software not as you've expected
+ // so I need to add these dirty hacks to mitigate unexpected issues
+ if (!tag.isMounted) { setMountState.call(tag, true); }
+
+ setMountState.call(tag, false);
+
+ delete root._tag;
+
+ return tag
+ }
+
+ /**
+ * Tag creation factory function
+ * @constructor
+ * @param { Object } impl - it contains the tag template, and logic
+ * @param { Object } conf - tag options
+ * @param { String } innerHTML - html that eventually we need to inject in the tag
+ */
+ function createTag(impl, conf, innerHTML) {
+ if ( impl === void 0 ) impl = {};
+ if ( conf === void 0 ) conf = {};
+
+ var tag = conf.context || {};
+ var opts = conf.opts || {};
+ var parent = conf.parent;
+ var isLoop = conf.isLoop;
+ var isAnonymous = !!conf.isAnonymous;
+ var skipAnonymous = settings.skipAnonymousTags && isAnonymous;
+ var item = conf.item;
+ // available only for the looped nodes
+ var index = conf.index;
+ // All attributes on the Tag when it's first parsed
+ var instAttrs = [];
+ // expressions on this type of Tag
+ var implAttrs = [];
+ var tmpl = impl.tmpl;
+ var expressions = [];
+ var root = conf.root;
+ var tagName = conf.tagName || getName(root);
+ var isVirtual = tagName === 'virtual';
+ var isInline = !isVirtual && !tmpl;
+ var dom;
+
+ if (isInline || isLoop && isAnonymous) {
+ dom = root;
+ } else {
+ if (!isVirtual) { root.innerHTML = ''; }
+ dom = mkdom(tmpl, innerHTML, isSvg(root));
+ }
+
+ // make this tag observable
+ if (!skipAnonymous) { observable(tag); }
+
+ // only call unmount if we have a valid __TAG_IMPL (has name property)
+ if (impl.name && root._tag) { root._tag.unmount(true); }
+
+ define(tag, '__', {
+ impl: impl,
+ root: root,
+ skipAnonymous: skipAnonymous,
+ implAttrs: implAttrs,
+ isAnonymous: isAnonymous,
+ instAttrs: instAttrs,
+ innerHTML: innerHTML,
+ tagName: tagName,
+ index: index,
+ isLoop: isLoop,
+ isInline: isInline,
+ item: item,
+ parent: parent,
+ // tags having event listeners
+ // it would be better to use weak maps here but we can not introduce breaking changes now
+ listeners: [],
+ // these vars will be needed only for the virtual tags
+ virts: [],
+ wasCreated: false,
+ tail: null,
+ head: null
+ });
+
+ // tag protected properties
+ return [
+ ['isMounted', false],
+ // create a unique id to this tag
+ // it could be handy to use it also to improve the virtual dom rendering speed
+ ['_riot_id', uid()],
+ ['root', root],
+ ['opts', opts, { writable: true, enumerable: true }],
+ ['parent', parent || null],
+ // protect the "tags" and "refs" property from being overridden
+ ['tags', {}],
+ ['refs', {}],
+ ['update', function (data) { return componentUpdate(tag, data, expressions); }],
+ ['mixin', function () {
+ var mixins = [], len = arguments.length;
+ while ( len-- ) mixins[ len ] = arguments[ len ];
+
+ return componentMixin.apply(void 0, [ tag ].concat( mixins ));
+ }],
+ ['mount', function () { return componentMount(tag, dom, expressions, opts); }],
+ ['unmount', function (mustKeepRoot) { return tagUnmount(tag, mustKeepRoot, expressions); }]
+ ].reduce(function (acc, ref) {
+ var key = ref[0];
+ var value = ref[1];
+ var opts = ref[2];
+
+ define(tag, key, value, opts);
+ return acc
+ }, extend(tag, item))
+ }
+
+ /**
+ * Mount a tag creating new Tag instance
+ * @param { Object } root - dom node where the tag will be mounted
+ * @param { String } tagName - name of the riot tag we want to mount
+ * @param { Object } opts - options to pass to the Tag instance
+ * @param { Object } ctx - optional context that will be used to extend an existing class ( used in riot.Tag )
+ * @returns { Tag } a new Tag instance
+ */
+ function mount$1(root, tagName, opts, ctx) {
+ var impl = __TAG_IMPL[tagName];
+ var implClass = __TAG_IMPL[tagName].class;
+ var context = ctx || (implClass ? create(implClass.prototype) : {});
+ // cache the inner HTML to fix #855
+ var innerHTML = root._innerHTML = root._innerHTML || root.innerHTML;
+ var conf = extend({ root: root, opts: opts, context: context }, { parent: opts ? opts.parent : null });
+ var tag;
+
+ if (impl && root) { tag = createTag(impl, conf, innerHTML); }
+
+ if (tag && tag.mount) {
+ tag.mount(true);
+ // add this tag to the virtualDom variable
+ if (!contains(__TAGS_CACHE, tag)) { __TAGS_CACHE.push(tag); }
+ }
+
+ return tag
+ }
+
+
+
+ var tags = /*#__PURE__*/Object.freeze({
+ arrayishAdd: arrayishAdd,
+ getTagName: getName,
+ inheritParentProps: inheritParentProps,
+ mountTo: mount$1,
+ selectTags: query,
+ arrayishRemove: arrayishRemove,
+ getTag: get,
+ initChildTag: initChild,
+ moveChildTag: moveChild,
+ makeReplaceVirtual: makeReplaceVirtual,
+ getImmediateCustomParentTag: getImmediateCustomParent,
+ makeVirtual: makeVirtual,
+ moveVirtual: moveVirtual,
+ unmountAll: unmountAll,
+ createIfDirective: createIfDirective,
+ createRefDirective: createRefDirective
+ });
+
+ /**
+ * Riot public api
+ */
+ var settings$1 = settings;
+ var util = {
+ tmpl: tmpl,
+ brackets: brackets,
+ styleManager: styleManager,
+ vdom: __TAGS_CACHE,
+ styleNode: styleManager.styleNode,
+ // export the riot internal utils as well
+ dom: dom,
+ check: check,
+ misc: misc,
+ tags: tags
+ };
+
+ // export the core props/methods
+ var Tag$1 = Tag;
+ var tag$1 = tag;
+ var tag2$1 = tag2;
+ var mount$2 = mount;
+ var mixin$1 = mixin;
+ var update$2 = update$1;
+ var unregister$1 = unregister;
+ var version$1 = version;
+ var observable$1 = observable;
+
+ var riot$1 = extend({}, core, {
+ observable: observable,
+ settings: settings$1,
+ util: util,
+ });
+
+ var riot$2 = /*#__PURE__*/Object.freeze({
+ settings: settings$1,
+ util: util,
+ Tag: Tag$1,
+ tag: tag$1,
+ tag2: tag2$1,
+ mount: mount$2,
+ mixin: mixin$1,
+ update: update$2,
+ unregister: unregister$1,
+ version: version$1,
+ observable: observable$1,
+ default: riot$1
+ });
+
+ /**
+ * Compiler for riot custom tags
+ * @version v3.5.2
+ */
+
+ // istanbul ignore next
+ function safeRegex (re) {
+ var arguments$1 = arguments;
+
+ var src = re.source;
+ var opt = re.global ? 'g' : '';
+
+ if (re.ignoreCase) { opt += 'i'; }
+ if (re.multiline) { opt += 'm'; }
+
+ for (var i = 1; i < arguments.length; i++) {
+ src = src.replace('@', '\\' + arguments$1[i]);
+ }
+
+ return new RegExp(src, opt)
+ }
+
+ /**
+ * @module parsers
+ */
+ var parsers = (function (win) {
+
+ var _p = {};
+
+ function _r (name) {
+ var parser = win[name];
+
+ if (parser) { return parser }
+
+ throw new Error('Parser "' + name + '" not loaded.')
+ }
+
+ function _req (name) {
+ var parts = name.split('.');
+
+ if (parts.length !== 2) { throw new Error('Bad format for parsers._req') }
+
+ var parser = _p[parts[0]][parts[1]];
+ if (parser) { return parser }
+
+ throw new Error('Parser "' + name + '" not found.')
+ }
+
+ function extend (obj, props) {
+ if (props) {
+ for (var prop in props) {
+ /* istanbul ignore next */
+ if (props.hasOwnProperty(prop)) {
+ obj[prop] = props[prop];
+ }
+ }
+ }
+ return obj
+ }
+
+ function renderPug (compilerName, html, opts, url) {
+ opts = extend({
+ pretty: true,
+ filename: url,
+ doctype: 'html'
+ }, opts);
+ return _r(compilerName).render(html, opts)
+ }
+
+ _p.html = {
+ jade: function (html, opts, url) {
+ /* eslint-disable */
+ console.log('DEPRECATION WARNING: jade was renamed "pug" - The jade parser will be removed in riot@3.0.0!');
+ /* eslint-enable */
+ return renderPug('jade', html, opts, url)
+ },
+ pug: function (html, opts, url) {
+ return renderPug('pug', html, opts, url)
+ }
+ };
+ _p.css = {
+ less: function (tag, css, opts, url) {
+ var ret;
+
+ opts = extend({
+ sync: true,
+ syncImport: true,
+ filename: url
+ }, opts);
+ _r('less').render(css, opts, function (err, result) {
+ // istanbul ignore next
+ if (err) { throw err }
+ ret = result.css;
+ });
+ return ret
+ }
+ };
+ _p.js = {
+
+ es6: function (js, opts, url) { // eslint-disable-line no-unused-vars
+ return _r('Babel').transform( // eslint-disable-line
+ js,
+ extend({
+ plugins: [
+ ['transform-es2015-template-literals', { loose: true }],
+ 'transform-es2015-literals',
+ 'transform-es2015-function-name',
+ 'transform-es2015-arrow-functions',
+ 'transform-es2015-block-scoped-functions',
+ ['transform-es2015-classes', { loose: true }],
+ 'transform-es2015-object-super',
+ 'transform-es2015-shorthand-properties',
+ 'transform-es2015-duplicate-keys',
+ ['transform-es2015-computed-properties', { loose: true }],
+ ['transform-es2015-for-of', { loose: true }],
+ 'transform-es2015-sticky-regex',
+ 'transform-es2015-unicode-regex',
+ 'check-es2015-constants',
+ ['transform-es2015-spread', { loose: true }],
+ 'transform-es2015-parameters',
+ ['transform-es2015-destructuring', { loose: true }],
+ 'transform-es2015-block-scoping',
+ 'transform-es2015-typeof-symbol',
+ ['transform-es2015-modules-commonjs', { allowTopLevelThis: true }],
+ ['transform-regenerator', { async: false, asyncGenerators: false }]
+ ]
+ },
+ opts
+ )).code
+ },
+ buble: function (js, opts, url) {
+ opts = extend({
+ source: url,
+ modules: false
+ }, opts);
+ return _r('buble').transform(js, opts).code
+ },
+ coffee: function (js, opts) {
+ return _r('CoffeeScript').compile(js, extend({ bare: true }, opts))
+ },
+ livescript: function (js, opts) {
+ return _r('livescript').compile(js, extend({ bare: true, header: false }, opts))
+ },
+ typescript: function (js, opts) {
+ return _r('typescript')(js, opts)
+ },
+ none: function (js) {
+ return js
+ }
+ };
+ _p.js.javascript = _p.js.none;
+ _p.js.coffeescript = _p.js.coffee;
+ _p._req = _req;
+ _p.utils = {
+ extend: extend
+ };
+
+ return _p
+
+ })(window || global);
+
+ var S_SQ_STR = /'[^'\n\r\\]*(?:\\(?:\r\n?|[\S\s])[^'\n\r\\]*)*'/.source;
+
+ var S_R_SRC1 = [
+ /\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//.source,
+ '//.*',
+ S_SQ_STR,
+ S_SQ_STR.replace(/'/g, '"'),
+ '([/`])'
+ ].join('|');
+
+ var S_R_SRC2 = (S_R_SRC1.slice(0, -2)) + "{}])";
+
+ function skipES6str (code, start, stack) {
+
+ var re = /[`$\\]/g;
+
+ re.lastIndex = start;
+ while (re.exec(code)) {
+ var end = re.lastIndex;
+ var c = code[end - 1];
+
+ if (c === '`') {
+ return end
+ }
+ if (c === '$' && code[end] === '{') {
+ stack.push('`', '}');
+ return end + 1
+ }
+ re.lastIndex++;
+ }
+
+ throw new Error('Unclosed ES6 template')
+ }
+
+ function jsSplitter (code, start) {
+
+ var re1 = new RegExp(S_R_SRC1, 'g');
+ var re2;
+
+ /* istanbul ignore next */
+ var skipRegex = brackets.skipRegex;
+ var offset = start | 0;
+ var result = [[]];
+ var stack = [];
+ var re = re1;
+
+ var lastPos = re.lastIndex = offset;
+ var str, ch, idx, end, match;
+
+ while ((match = re.exec(code))) {
+ idx = match.index;
+ end = re.lastIndex;
+ str = '';
+ ch = match[1];
+
+ if (ch) {
+
+ if (ch === '{') {
+ stack.push('}');
+
+ } else if (ch === '}') {
+ if (stack.pop() !== ch) {
+ throw new Error("Unexpected '}'")
+
+ } else if (stack[stack.length - 1] === '`') {
+ ch = stack.pop();
+ }
+
+ } else if (ch === '/') {
+ end = skipRegex(code, idx);
+
+ if (end > idx + 1) {
+ str = code.slice(idx, end);
+ }
+ }
+
+ if (ch === '`') {
+ end = skipES6str(code, end, stack);
+ str = code.slice(idx, end);
+
+ if (stack.length) {
+ re = re2 || (re2 = new RegExp(S_R_SRC2, 'g'));
+ } else {
+ re = re1;
+ }
+ }
+
+ } else {
+
+ str = match[0];
+
+ if (str[0] === '/') {
+ str = str[1] === '*' ? ' ' : '';
+ code = code.slice(offset, idx) + str + code.slice(end);
+ end = idx + str.length;
+ str = '';
+
+ } else if (str.length === 2) {
+ str = '';
+ }
+ }
+
+ if (str) {
+ result[0].push(code.slice(lastPos, idx));
+ result.push(str);
+ lastPos = end;
+ }
+
+ re.lastIndex = end;
+ }
+
+ result[0].push(code.slice(lastPos));
+
+ return result
+ }
+
+ /**
+ * @module compiler
+ */
+
+ var extend$1 = parsers.utils.extend;
+ /* eslint-enable */
+
+ var S_LINESTR = /"[^"\n\\]*(?:\\[\S\s][^"\n\\]*)*"|'[^'\n\\]*(?:\\[\S\s][^'\n\\]*)*'/.source;
+
+ var S_STRINGS = brackets.R_STRINGS.source;
+
+ var HTML_ATTRS = / *([-\w:\xA0-\xFF]+) ?(?:= ?('[^']*'|"[^"]*"|\S+))?/g;
+
+ var HTML_COMMS = RegExp(//.source + '|' + S_LINESTR, 'g');
+
+ var HTML_TAGS = /<(-?[A-Za-z][-\w\xA0-\xFF]*)(?:\s+([^"'/>]*(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"/>]*)*)|\s*)(\/?)>/g;
+
+ var HTML_PACK = />[ \t]+<(-?[A-Za-z]|\/[-A-Za-z])/g;
+
+ var RIOT_ATTRS = ['style', 'src', 'd', 'value'];
+
+ var VOID_TAGS = /^(?:input|img|br|wbr|hr|area|base|col|embed|keygen|link|meta|param|source|track)$/;
+
+ var PRE_TAGS = /]*|"[^"]*")*)?>([\S\s]+?)<\/pre\s*>/gi;
+
+ var SPEC_TYPES = /^"(?:number|date(?:time)?|time|month|email|color)\b/i;
+
+ var IMPORT_STATEMENT = /^\s*import(?!\w|(\s)?\()(?:(?:\s|[^\s'"])*)['|"].*\n?/gm;
+
+ var TRIM_TRAIL = /[ \t]+$/gm;
+
+ var
+ RE_HASEXPR = safeRegex(/@#\d/, 'x01'),
+ RE_REPEXPR = safeRegex(/@#(\d+)/g, 'x01'),
+ CH_IDEXPR = '\x01#',
+ CH_DQCODE = '\u2057',
+ DQ = '"',
+ SQ = "'";
+
+ function cleanSource (src) {
+ var
+ mm,
+ re = HTML_COMMS;
+
+ if (src.indexOf('\r') !== 1) {
+ src = src.replace(/\r\n?/g, '\n');
+ }
+
+ re.lastIndex = 0;
+ while ((mm = re.exec(src))) {
+ if (mm[0][0] === '<') {
+ src = RegExp.leftContext + RegExp.rightContext;
+ re.lastIndex = mm[3] + 1;
+ }
+ }
+ return src
+ }
+
+ function parseAttribs (str, pcex) {
+ var
+ list = [],
+ match,
+ type, vexp;
+
+ HTML_ATTRS.lastIndex = 0;
+
+ str = str.replace(/\s+/g, ' ');
+
+ while ((match = HTML_ATTRS.exec(str))) {
+ var
+ k = match[1].toLowerCase(),
+ v = match[2];
+
+ if (!v) {
+ list.push(k);
+ } else {
+
+ if (v[0] !== DQ) {
+ v = DQ + (v[0] === SQ ? v.slice(1, -1) : v) + DQ;
+ }
+
+ if (k === 'type' && SPEC_TYPES.test(v)) {
+ type = v;
+ } else {
+ if (RE_HASEXPR.test(v)) {
+
+ if (k === 'value') { vexp = 1; }
+ if (RIOT_ATTRS.indexOf(k) !== -1) { k = 'riot-' + k; }
+ }
+
+ list.push(k + '=' + v);
+ }
+ }
+ }
+
+ if (type) {
+ if (vexp) { type = DQ + pcex._bp[0] + SQ + type.slice(1, -1) + SQ + pcex._bp[1] + DQ; }
+ list.push('type=' + type);
+ }
+ return list.join(' ')
+ }
+
+ function splitHtml (html, opts, pcex) {
+ var _bp = pcex._bp;
+
+ if (html && _bp[4].test(html)) {
+ var
+ jsfn = opts.expr && (opts.parser || opts.type) ? _compileJS : 0,
+ list = brackets.split(html, 0, _bp),
+ expr;
+
+ for (var i = 1; i < list.length; i += 2) {
+ expr = list[i];
+ if (expr[0] === '^') {
+ expr = expr.slice(1);
+ } else if (jsfn) {
+ expr = jsfn(expr, opts).trim();
+ if (expr.slice(-1) === ';') { expr = expr.slice(0, -1); }
+ }
+ list[i] = CH_IDEXPR + (pcex.push(expr) - 1) + _bp[1];
+ }
+ html = list.join('');
+ }
+ return html
+ }
+
+ function restoreExpr (html, pcex) {
+ if (pcex.length) {
+ html = html.replace(RE_REPEXPR, function (_, d) {
+
+ return pcex._bp[0] + pcex[d].trim().replace(/[\r\n]+/g, ' ').replace(/"/g, CH_DQCODE)
+ });
+ }
+ return html
+ }
+
+ function _compileHTML (html, opts, pcex) {
+ if (!/\S/.test(html)) { return '' }
+
+ html = splitHtml(html, opts, pcex)
+ .replace(HTML_TAGS, function (_, name, attr, ends) {
+
+ name = name.toLowerCase();
+
+ ends = ends && !VOID_TAGS.test(name) ? '>' + name : '';
+
+ if (attr) { name += ' ' + parseAttribs(attr, pcex); }
+
+ return '<' + name + ends + '>'
+ });
+
+ if (!opts.whitespace) {
+ var p = [];
+
+ if (/]/.test(html)) {
+ html = html.replace(PRE_TAGS, function (q) {
+ p.push(q);
+ return '\u0002'
+ });
+ }
+
+ html = html.trim().replace(/\s+/g, ' ');
+
+ if (p.length) { html = html.replace(/\u0002/g, function () { return p.shift() }); } // eslint-disable-line
+ }
+
+ if (opts.compact) { html = html.replace(HTML_PACK, '><$1'); }
+
+ return restoreExpr(html, pcex).replace(TRIM_TRAIL, '')
+ }
+
+ function compileHTML (html, opts, pcex) {
+
+ if (Array.isArray(opts)) {
+ pcex = opts;
+ opts = {};
+ } else {
+ if (!pcex) { pcex = []; }
+ if (!opts) { opts = {}; }
+ }
+
+ pcex._bp = brackets.array(opts.brackets);
+
+ return _compileHTML(cleanSource(html), opts, pcex)
+ }
+
+ var JS_ES6SIGN = /^[ \t]*(((?:async|\*)\s*)?([$_A-Za-z][$\w]*))\s*\([^()]*\)\s*{/m;
+
+ function riotjs (js) {
+ var
+ parts = [],
+ match,
+ toes5,
+ pos,
+ method,
+ prefix,
+ name,
+ RE = RegExp;
+
+ var src = jsSplitter(js);
+ js = src.shift().join('<%>');
+
+ while ((match = js.match(JS_ES6SIGN))) {
+
+ parts.push(RE.leftContext);
+ js = RE.rightContext;
+ pos = skipBody(js);
+
+ method = match[1];
+ prefix = match[2] || '';
+ name = match[3];
+
+ toes5 = !/^(?:if|while|for|switch|catch|function)$/.test(name);
+
+ if (toes5) {
+ name = match[0].replace(method, 'this.' + name + ' =' + prefix + ' function');
+ } else {
+ name = match[0];
+ }
+
+ parts.push(name, js.slice(0, pos));
+ js = js.slice(pos);
+
+ if (toes5 && !/^\s*.\s*bind\b/.test(js)) { parts.push('.bind(this)'); }
+ }
+
+ if (parts.length) {
+ js = parts.join('') + js;
+ }
+
+ if (src.length) {
+ js = js.replace(/<%>/g, function () {
+ return src.shift()
+ });
+ }
+
+ return js
+
+ function skipBody (s) {
+ var r = /[{}]/g;
+ var i = 1;
+
+ while (i && r.exec(s)) {
+ if (s[r.lastIndex - 1] === '{') { ++i; }
+ else { --i; }
+ }
+ return i ? s.length : r.lastIndex
+ }
+ }
+
+ function _compileJS (js, opts, type, parserOpts, url) {
+ if (!/\S/.test(js)) { return '' }
+ if (!type) { type = opts.type; }
+
+ var parser = opts.parser || type && parsers._req('js.' + type, true) || riotjs;
+
+ return parser(js, parserOpts, url).replace(/\r\n?/g, '\n').replace(TRIM_TRAIL, '')
+ }
+
+ function compileJS (js, opts, type, userOpts) {
+ if (typeof opts === 'string') {
+ userOpts = type;
+ type = opts;
+ opts = {};
+ }
+ if (type && typeof type === 'object') {
+ userOpts = type;
+ type = '';
+ }
+ if (!userOpts) { userOpts = {}; }
+
+ return _compileJS(js, opts || {}, type, userOpts.parserOptions, userOpts.url)
+ }
+
+ var CSS_SELECTOR = RegExp('([{}]|^)[; ]*((?:[^@ ;{}][^{}]*)?[^@ ;{}:] ?)(?={)|' + S_LINESTR, 'g');
+
+ function scopedCSS (tag, css) {
+ var scope = ':scope';
+
+ return css.replace(CSS_SELECTOR, function (m, p1, p2) {
+
+ if (!p2) { return m }
+
+ p2 = p2.replace(/[^,]+/g, function (sel) {
+ var s = sel.trim();
+
+ if (s.indexOf(tag) === 0) {
+ return sel
+ }
+
+ if (!s || s === 'from' || s === 'to' || s.slice(-1) === '%') {
+ return sel
+ }
+
+ if (s.indexOf(scope) < 0) {
+ s = tag + ' ' + s + ',[data-is="' + tag + '"] ' + s;
+ } else {
+ s = s.replace(scope, tag) + ',' +
+ s.replace(scope, '[data-is="' + tag + '"]');
+ }
+ return s
+ });
+
+ return p1 ? p1 + ' ' + p2 : p2
+ })
+ }
+
+ function _compileCSS (css, tag, type, opts) {
+ opts = opts || {};
+
+ if (type) {
+ if (type !== 'css') {
+
+ var parser = parsers._req('css.' + type, true);
+ css = parser(tag, css, opts.parserOpts || {}, opts.url);
+ }
+ }
+
+ css = css.replace(brackets.R_MLCOMMS, '').replace(/\s+/g, ' ').trim();
+ if (tag) { css = scopedCSS(tag, css); }
+
+ return css
+ }
+
+ function compileCSS (css, type, opts) {
+ if (type && typeof type === 'object') {
+ opts = type;
+ type = '';
+ } else if (!opts) { opts = {}; }
+
+ return _compileCSS(css, opts.tagName, type, opts)
+ }
+
+ var TYPE_ATTR = /\stype\s*=\s*(?:(['"])(.+?)\1|(\S+))/i;
+
+ var MISC_ATTR = '\\s*=\\s*(' + S_STRINGS + '|{[^}]+}|\\S+)';
+
+ var END_TAGS = /\/>\n|^<(?:\/?-?[A-Za-z][-\w\xA0-\xFF]*\s*|-?[A-Za-z][-\w\xA0-\xFF]*\s+[-\w:\xA0-\xFF][\S\s]*?)>\n/;
+
+ function _q (s, r) {
+ if (!s) { return "''" }
+ s = SQ + s.replace(/\\/g, '\\\\').replace(/'/g, "\\'") + SQ;
+ return r && s.indexOf('\n') !== -1 ? s.replace(/\n/g, '\\n') : s
+ }
+
+ function mktag (name, html, css, attr, js, imports, opts) {
+ var
+ c = opts.debug ? ',\n ' : ', ',
+ s = '});';
+
+ if (js && js.slice(-1) !== '\n') { s = '\n' + s; }
+
+ return imports + 'riot.tag2(\'' + name + SQ +
+ c + _q(html, 1) +
+ c + _q(css) +
+ c + _q(attr) + ', function(opts) {\n' + js + s
+ }
+
+ function splitBlocks (str) {
+ if (/<[-\w]/.test(str)) {
+ var
+ m,
+ k = str.lastIndexOf('<'),
+ n = str.length;
+
+ while (k !== -1) {
+ m = str.slice(k, n).match(END_TAGS);
+ if (m) {
+ k += m.index + m[0].length;
+ m = str.slice(0, k);
+ if (m.slice(-5) === '<-/>\n') { m = m.slice(0, -5); }
+ return [m, str.slice(k)]
+ }
+ n = k;
+ k = str.lastIndexOf('<', k - 1);
+ }
+ }
+ return ['', str]
+ }
+
+ function getType (attribs) {
+ if (attribs) {
+ var match = attribs.match(TYPE_ATTR);
+
+ match = match && (match[2] || match[3]);
+ if (match) {
+ return match.replace('text/', '')
+ }
+ }
+ return ''
+ }
+
+ function getAttrib (attribs, name) {
+ if (attribs) {
+ var match = attribs.match(RegExp('\\s' + name + MISC_ATTR, 'i'));
+
+ match = match && match[1];
+ if (match) {
+ return (/^['"]/).test(match) ? match.slice(1, -1) : match
+ }
+ }
+ return ''
+ }
+
+ function unescapeHTML (str) {
+ return str
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, '\'')
+ }
+
+ function getParserOptions (attribs) {
+ var opts = unescapeHTML(getAttrib(attribs, 'options'));
+
+ return opts ? JSON.parse(opts) : null
+ }
+
+ function getCode (code, opts, attribs, base) {
+ var
+ type = getType(attribs),
+ src = getAttrib(attribs, 'src'),
+ jsParserOptions = extend$1({}, opts.parserOptions.js);
+
+ if (src) { return false }
+
+ return _compileJS(
+ code,
+ opts,
+ type,
+ extend$1(jsParserOptions, getParserOptions(attribs)),
+ base
+ )
+ }
+
+ function cssCode (code, opts, attribs, url, tag) {
+ var
+ parserStyleOptions = extend$1({}, opts.parserOptions.style),
+ extraOpts = {
+ parserOpts: extend$1(parserStyleOptions, getParserOptions(attribs)),
+ url: url
+ };
+
+ return _compileCSS(code, tag, getType(attribs) || opts.style, extraOpts)
+ }
+
+ function compileTemplate (html, url, lang, opts) {
+
+ var parser = parsers._req('html.' + lang, true);
+ return parser(html, opts, url)
+ }
+
+ var
+
+ CUST_TAG = RegExp(/^([ \t]*)<(-?[A-Za-z][-\w\xA0-\xFF]*)(?:\s+([^'"/>]+(?:(?:@|\/[^>])[^'"/>]*)*)|\s*)?(?:\/>|>[ \t]*\n?([\S\s]*)^\1<\/\2\s*>|>(.*)<\/\2\s*>)/
+ .source.replace('@', S_STRINGS), 'gim'),
+
+ SCRIPTS = /
+
ADDED public/riot-3.13.2/test/performance/db-monster/dbmon.tag
Index: public/riot-3.13.2/test/performance/db-monster/dbmon.tag
==================================================================
--- public/riot-3.13.2/test/performance/db-monster/dbmon.tag
+++ public/riot-3.13.2/test/performance/db-monster/dbmon.tag
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+ { dbname }
+
+
+
+
+ { lastSample.nbQueries }
+
+
+
+
+ { formatElapsed }
+
+
+
+
+
+
+
+
ADDED public/riot-3.13.2/test/performance/db-monster/index.html
Index: public/riot-3.13.2/test/performance/db-monster/index.html
==================================================================
--- public/riot-3.13.2/test/performance/db-monster/index.html
+++ public/riot-3.13.2/test/performance/db-monster/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ dbmon (riot)
+
+
+
+
+
+
+
+
+
+
+
ADDED public/riot-3.13.2/test/performance/db-monster/memory-stats.js
Index: public/riot-3.13.2/test/performance/db-monster/memory-stats.js
==================================================================
--- public/riot-3.13.2/test/performance/db-monster/memory-stats.js
+++ public/riot-3.13.2/test/performance/db-monster/memory-stats.js
@@ -0,0 +1,101 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author jetienne / http://jetienne.com/
+ * @author paulirish / http://paulirish.com/
+ */
+var MemoryStats = function (){
+
+ var msMin = 100;
+ var msMax = 0;
+
+ var container = document.createElement( 'div' );
+ container.id = 'stats';
+ container.style.cssText = 'width:80px;opacity:0.9;cursor:pointer';
+
+ var msDiv = document.createElement( 'div' );
+ msDiv.id = 'ms';
+ msDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#020;';
+ container.appendChild( msDiv );
+
+ var msText = document.createElement( 'div' );
+ msText.id = 'msText';
+ msText.style.cssText = 'color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px';
+ msText.innerHTML= 'Memory';
+ msDiv.appendChild( msText );
+
+ var msGraph = document.createElement( 'div' );
+ msGraph.id = 'msGraph';
+ msGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0f0';
+ msDiv.appendChild( msGraph );
+
+ while ( msGraph.children.length < 74 ) {
+
+ var bar = document.createElement( 'span' );
+ bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#131';
+ msGraph.appendChild( bar );
+
+ }
+
+ var updateGraph = function ( dom, height, color ) {
+
+ var child = dom.appendChild( dom.firstChild );
+ child.style.height = height + 'px';
+ if( color ) child.style.backgroundColor = color;
+
+ }
+
+ var perf = window.performance || {};
+ // polyfill usedJSHeapSize
+ if (!perf && !perf.memory){
+ perf.memory = { usedJSHeapSize : 0 };
+ }
+ if (perf && !perf.memory){
+ perf.memory = { usedJSHeapSize : 0 };
+ }
+
+ // support of the API?
+ if( perf.memory.totalJSHeapSize === 0 ){
+ console.warn('totalJSHeapSize === 0... performance.memory is only available in Chrome .')
+ }
+
+ // TODO, add a sanity check to see if values are bucketed.
+ // If so, reminde user to adopt the --enable-precise-memory-info flag.
+ // open -a "/Applications/Google Chrome.app" --args --enable-precise-memory-info
+
+ var lastTime = Date.now();
+ var lastUsedHeap= perf.memory.usedJSHeapSize;
+ return {
+ domElement: container,
+
+ update: function () {
+
+ // refresh only 30time per second
+ if( Date.now() - lastTime < 1000/30 ) return;
+ lastTime = Date.now()
+
+ var delta = perf.memory.usedJSHeapSize - lastUsedHeap;
+ lastUsedHeap = perf.memory.usedJSHeapSize;
+ var color = delta < 0 ? '#830' : '#131';
+
+ var ms = perf.memory.usedJSHeapSize;
+ msMin = Math.min( msMin, ms );
+ msMax = Math.max( msMax, ms );
+ msText.textContent = "Mem: " + bytesToSize(ms, 2);
+
+ var normValue = ms / (30*1024*1024);
+ var height = Math.min( 30, 30 - normValue * 30 );
+ updateGraph( msGraph, height, color);
+
+ function bytesToSize( bytes, nFractDigit ){
+ var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+ if (bytes == 0) return 'n/a';
+ nFractDigit = nFractDigit !== undefined ? nFractDigit : 0;
+ var precision = Math.pow(10, nFractDigit);
+ var i = Math.floor(Math.log(bytes) / Math.log(1024));
+ return Math.round(bytes*precision / Math.pow(1024, i))/precision + ' ' + sizes[i];
+ };
+ }
+
+ }
+
+};
ADDED public/riot-3.13.2/test/performance/db-monster/monitor.js
Index: public/riot-3.13.2/test/performance/db-monster/monitor.js
==================================================================
--- public/riot-3.13.2/test/performance/db-monster/monitor.js
+++ public/riot-3.13.2/test/performance/db-monster/monitor.js
@@ -0,0 +1,60 @@
+var Monitoring = Monitoring || (function() {
+
+ var stats = new MemoryStats();
+ stats.domElement.style.position = 'fixed';
+ stats.domElement.style.right = '0px';
+ stats.domElement.style.bottom = '0px';
+ document.body.appendChild( stats.domElement );
+ requestAnimationFrame(function rAFloop(){
+ stats.update();
+ requestAnimationFrame(rAFloop);
+ });
+
+ var RenderRate = function () {
+ var container = document.createElement( 'div' );
+ container.id = 'stats';
+ container.style.cssText = 'width:150px;opacity:0.9;cursor:pointer;position:fixed;right:80px;bottom:0px;';
+
+ var msDiv = document.createElement( 'div' );
+ msDiv.id = 'ms';
+ msDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#020;';
+ container.appendChild( msDiv );
+
+ var msText = document.createElement( 'div' );
+ msText.id = 'msText';
+ msText.style.cssText = 'color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px';
+ msText.innerHTML= 'Repaint rate: 0/sec';
+ msDiv.appendChild( msText );
+
+ var bucketSize = 20;
+ var bucket = [];
+ var lastTime = Date.now();
+ return {
+ domElement: container,
+ ping: function () {
+ var start = lastTime;
+ var stop = Date.now();
+ var rate = 1000 / (stop - start);
+ bucket.push(rate);
+ if (bucket.length > bucketSize) {
+ bucket.shift();
+ }
+ var sum = 0;
+ for (var i = 0; i < bucket.length; i++) {
+ sum = sum + bucket[i];
+ }
+ msText.textContent = "Repaint rate: " + (sum / bucket.length).toFixed(2) + "/sec";
+ lastTime = stop;
+ }
+ }
+ };
+
+ var renderRate = new RenderRate();
+ document.body.appendChild( renderRate.domElement );
+
+ return {
+ memoryStats: stats,
+ renderRate: renderRate
+ };
+
+})();
ADDED public/riot-3.13.2/test/performance/db-monster/no-reorder.html
Index: public/riot-3.13.2/test/performance/db-monster/no-reorder.html
==================================================================
--- public/riot-3.13.2/test/performance/db-monster/no-reorder.html
+++ public/riot-3.13.2/test/performance/db-monster/no-reorder.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ dbmon (riot)
+
+
+
+
+
+
+
+
+
+
+
ADDED public/riot-3.13.2/test/performance/db-monster/styles.css
Index: public/riot-3.13.2/test/performance/db-monster/styles.css
==================================================================
--- public/riot-3.13.2/test/performance/db-monster/styles.css
+++ public/riot-3.13.2/test/performance/db-monster/styles.css
@@ -0,0 +1,10 @@
+.Query {
+ position: relative;
+}
+
+.Query:hover .popover {
+ left: -100%;
+ width: 100%;
+ display: block;
+}
+
ADDED public/riot-3.13.2/test/performance/loop.html
Index: public/riot-3.13.2/test/performance/loop.html
==================================================================
--- public/riot-3.13.2/test/performance/loop.html
+++ public/riot-3.13.2/test/performance/loop.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ADDED public/riot-3.13.2/test/performance/memory/index.js
Index: public/riot-3.13.2/test/performance/memory/index.js
==================================================================
--- public/riot-3.13.2/test/performance/memory/index.js
+++ public/riot-3.13.2/test/performance/memory/index.js
@@ -0,0 +1,105 @@
+/**
+ *
+ * You can check the live example on here http://jsfiddle.net/gianlucaguarini/ftoLgfg3/
+ *
+ */
+
+var riot = require('../../../dist/riot/riot'),
+ jsdom = require('jsdom').JSDOM,
+ myComponent = 'my-component',
+ myComponentHTML = `
+ '{ opts.title } ',
+ '{ opts.description }
',
+ ''
+ `,
+ result,
+ myListItem = 'my-list-item',
+ myListItemHTML = `
+ ' ',
+ 'I am active '
+ `
+
+
+/**
+ * Helper function to generate custom array
+ * @param { int } amount amount of entries in the array
+ * @param { * } data
+ * @return array
+ */
+
+
+function generateItems (amount) {
+ var items = []
+ while (--amount) {
+ items.push({
+ isActive: false
+ })
+ }
+ return items
+}
+
+/**
+ * Check the memory usage analizing the heap
+ * @param { function } fn
+ * @return { array } memory used + duration
+ */
+
+function measure(fn) {
+ var startTime = Date.now()
+ fn()
+ global.gc()
+ return [process.memoryUsage().heapUsed, Date.now() - startTime]
+}
+
+/**
+ * Adding the custom tags to the riot internal cache
+ */
+
+function setupTags() {
+ riot.tag(myComponent, myComponentHTML, function(opts) {
+ var self = this
+ function loop () {
+ opts.items = generateItems(1000, {
+ isActive: false
+ })
+ result = measure(self.update.bind(self))
+ console.log(
+ (result[0] / 1024 / 1024).toFixed(2) + ' MiB',
+ result[1] + ' ms'
+ )
+ setTimeout(loop, 50)
+ }
+ loop()
+ })
+ riot.tag(myListItem, myListItemHTML, function (opts) {
+ this.onChange = function (e) {
+ opts.isActive = e.target.checked
+ }
+ })
+}
+
+/**
+ *
+ * Mount the custom tags passing some fake values
+ *
+ */
+
+function mount() {
+ riot.mount(myComponent, {
+ title: 'hello world',
+ description: 'mad world',
+ items: generateItems(1000, {
+ isActive: false
+ })
+ })
+}
+
+
+// Initialize the test
+var doc = new jsdom(`<${myComponent}/>`)
+global.window = doc.defaultView
+global.document = doc.window.document
+
+setupTags()
+mount()
+
ADDED public/riot-3.13.2/test/performance/riot.2.6.1.js
Index: public/riot-3.13.2/test/performance/riot.2.6.1.js
==================================================================
--- public/riot-3.13.2/test/performance/riot.2.6.1.js
+++ public/riot-3.13.2/test/performance/riot.2.6.1.js
@@ -0,0 +1,2669 @@
+/* Riot v2.6.1, @license MIT */
+
+;(function(window, undefined) {
+ 'use strict';
+var riot = { version: 'v2.6.1', settings: {} },
+ // be aware, internal usage
+ // ATTENTION: prefix the global dynamic variables with `__`
+
+ // counter to give a unique id to all the Tag instances
+ __uid = 0,
+ // tags instances cache
+ __virtualDom = [],
+ // tags implementation cache
+ __tagImpl = {},
+
+ /**
+ * Const
+ */
+ GLOBAL_MIXIN = '__global_mixin',
+
+ // riot specific prefixes
+ RIOT_PREFIX = 'riot-',
+ RIOT_TAG = RIOT_PREFIX + 'tag',
+ RIOT_TAG_IS = 'data-is',
+
+ // for typeof == '' comparisons
+ T_STRING = 'string',
+ T_OBJECT = 'object',
+ T_UNDEF = 'undefined',
+ T_FUNCTION = 'function',
+ XLINK_NS = 'http://www.w3.org/1999/xlink',
+ XLINK_REGEX = /^xlink:(\w+)/,
+ // special native tags that cannot be treated like the others
+ SPECIAL_TAGS_REGEX = /^(?:t(?:body|head|foot|[rhd])|caption|col(?:group)?|opt(?:ion|group))$/,
+ RESERVED_WORDS_BLACKLIST = /^(?:_(?:item|id|parent)|update|root|(?:un)?mount|mixin|is(?:Mounted|Loop)|tags|parent|opts|trigger|o(?:n|ff|ne))$/,
+ // SVG tags list https://www.w3.org/TR/SVG/attindex.html#PresentationAttributes
+ SVG_TAGS_LIST = ['altGlyph', 'animate', 'animateColor', 'circle', 'clipPath', 'defs', 'ellipse', 'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feFlood', 'feGaussianBlur', 'feImage', 'feMerge', 'feMorphology', 'feOffset', 'feSpecularLighting', 'feTile', 'feTurbulence', 'filter', 'font', 'foreignObject', 'g', 'glyph', 'glyphRef', 'image', 'line', 'linearGradient', 'marker', 'mask', 'missing-glyph', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'svg', 'switch', 'symbol', 'text', 'textPath', 'tref', 'tspan', 'use'],
+
+ // version# for IE 8-11, 0 for others
+ IE_VERSION = (window && window.document || {}).documentMode | 0,
+
+ // detect firefox to fix #1374
+ FIREFOX = window && !!window.InstallTrigger
+/* istanbul ignore next */
+riot.observable = function(el) {
+
+ /**
+ * Extend the original object or create a new empty one
+ * @type { Object }
+ */
+
+ el = el || {}
+
+ /**
+ * Private variables
+ */
+ var callbacks = {},
+ slice = Array.prototype.slice
+
+ /**
+ * Private Methods
+ */
+
+ /**
+ * Helper function needed to get and loop all the events in a string
+ * @param { String } e - event string
+ * @param {Function} fn - callback
+ */
+ function onEachEvent(e, fn) {
+ var es = e.split(' '), l = es.length, i = 0
+ for (; i < l; i++) {
+ var name = es[i]
+ if (name) fn(name, i)
+ }
+ }
+
+ /**
+ * Public Api
+ */
+
+ // extend the el object adding the observable methods
+ Object.defineProperties(el, {
+ /**
+ * Listen to the given space separated list of `events` and
+ * execute the `callback` each time an event is triggered.
+ * @param { String } events - events ids
+ * @param { Function } fn - callback function
+ * @returns { Object } el
+ */
+ on: {
+ value: function(events, fn) {
+ if (typeof fn != 'function') return el
+
+ onEachEvent(events, function(name, pos) {
+ (callbacks[name] = callbacks[name] || []).push(fn)
+ fn.typed = pos > 0
+ })
+
+ return el
+ },
+ enumerable: false,
+ writable: false,
+ configurable: false
+ },
+
+ /**
+ * Removes the given space separated list of `events` listeners
+ * @param { String } events - events ids
+ * @param { Function } fn - callback function
+ * @returns { Object } el
+ */
+ off: {
+ value: function(events, fn) {
+ if (events == '*' && !fn) callbacks = {}
+ else {
+ onEachEvent(events, function(name, pos) {
+ if (fn) {
+ var arr = callbacks[name]
+ for (var i = 0, cb; cb = arr && arr[i]; ++i) {
+ if (cb == fn) arr.splice(i--, 1)
+ }
+ } else delete callbacks[name]
+ })
+ }
+ return el
+ },
+ enumerable: false,
+ writable: false,
+ configurable: false
+ },
+
+ /**
+ * Listen to the given space separated list of `events` and
+ * execute the `callback` at most once
+ * @param { String } events - events ids
+ * @param { Function } fn - callback function
+ * @returns { Object } el
+ */
+ one: {
+ value: function(events, fn) {
+ function on() {
+ el.off(events, on)
+ fn.apply(el, arguments)
+ }
+ return el.on(events, on)
+ },
+ enumerable: false,
+ writable: false,
+ configurable: false
+ },
+
+ /**
+ * Execute all callback functions that listen to
+ * the given space separated list of `events`
+ * @param { String } events - events ids
+ * @returns { Object } el
+ */
+ trigger: {
+ value: function(events) {
+
+ // getting the arguments
+ var arglen = arguments.length - 1,
+ args = new Array(arglen),
+ fns
+
+ for (var i = 0; i < arglen; i++) {
+ args[i] = arguments[i + 1] // skip first argument
+ }
+
+ onEachEvent(events, function(name, pos) {
+
+ fns = slice.call(callbacks[name] || [], 0)
+
+ for (var i = 0, fn; fn = fns[i]; ++i) {
+ if (fn.busy) continue
+ fn.busy = 1
+ fn.apply(el, fn.typed ? [name].concat(args) : args)
+ if (fns[i] !== fn) { i-- }
+ fn.busy = 0
+ }
+
+ if (callbacks['*'] && name != '*')
+ el.trigger.apply(el, ['*', name].concat(args))
+
+ })
+
+ return el
+ },
+ enumerable: false,
+ writable: false,
+ configurable: false
+ }
+ })
+
+ return el
+
+}
+/* istanbul ignore next */
+;(function(riot) {
+
+/**
+ * Simple client-side router
+ * @module riot-route
+ */
+
+
+var RE_ORIGIN = /^.+?\/\/+[^\/]+/,
+ EVENT_LISTENER = 'EventListener',
+ REMOVE_EVENT_LISTENER = 'remove' + EVENT_LISTENER,
+ ADD_EVENT_LISTENER = 'add' + EVENT_LISTENER,
+ HAS_ATTRIBUTE = 'hasAttribute',
+ REPLACE = 'replace',
+ POPSTATE = 'popstate',
+ HASHCHANGE = 'hashchange',
+ TRIGGER = 'trigger',
+ MAX_EMIT_STACK_LEVEL = 3,
+ win = typeof window != 'undefined' && window,
+ doc = typeof document != 'undefined' && document,
+ hist = win && history,
+ loc = win && (hist.location || win.location), // see html5-history-api
+ prot = Router.prototype, // to minify more
+ clickEvent = doc && doc.ontouchstart ? 'touchstart' : 'click',
+ started = false,
+ central = riot.observable(),
+ routeFound = false,
+ debouncedEmit,
+ base, current, parser, secondParser, emitStack = [], emitStackLevel = 0
+
+/**
+ * Default parser. You can replace it via router.parser method.
+ * @param {string} path - current path (normalized)
+ * @returns {array} array
+ */
+function DEFAULT_PARSER(path) {
+ return path.split(/[/?#]/)
+}
+
+/**
+ * Default parser (second). You can replace it via router.parser method.
+ * @param {string} path - current path (normalized)
+ * @param {string} filter - filter string (normalized)
+ * @returns {array} array
+ */
+function DEFAULT_SECOND_PARSER(path, filter) {
+ var re = new RegExp('^' + filter[REPLACE](/\*/g, '([^/?#]+?)')[REPLACE](/\.\./, '.*') + '$'),
+ args = path.match(re)
+
+ if (args) return args.slice(1)
+}
+
+/**
+ * Simple/cheap debounce implementation
+ * @param {function} fn - callback
+ * @param {number} delay - delay in seconds
+ * @returns {function} debounced function
+ */
+function debounce(fn, delay) {
+ var t
+ return function () {
+ clearTimeout(t)
+ t = setTimeout(fn, delay)
+ }
+}
+
+/**
+ * Set the window listeners to trigger the routes
+ * @param {boolean} autoExec - see route.start
+ */
+function start(autoExec) {
+ debouncedEmit = debounce(emit, 1)
+ win[ADD_EVENT_LISTENER](POPSTATE, debouncedEmit)
+ win[ADD_EVENT_LISTENER](HASHCHANGE, debouncedEmit)
+ doc[ADD_EVENT_LISTENER](clickEvent, click)
+ if (autoExec) emit(true)
+}
+
+/**
+ * Router class
+ */
+function Router() {
+ this.$ = []
+ riot.observable(this) // make it observable
+ central.on('stop', this.s.bind(this))
+ central.on('emit', this.e.bind(this))
+}
+
+function normalize(path) {
+ return path[REPLACE](/^\/|\/$/, '')
+}
+
+function isString(str) {
+ return typeof str == 'string'
+}
+
+/**
+ * Get the part after domain name
+ * @param {string} href - fullpath
+ * @returns {string} path from root
+ */
+function getPathFromRoot(href) {
+ return (href || loc.href)[REPLACE](RE_ORIGIN, '')
+}
+
+/**
+ * Get the part after base
+ * @param {string} href - fullpath
+ * @returns {string} path from base
+ */
+function getPathFromBase(href) {
+ return base[0] == '#'
+ ? (href || loc.href || '').split(base)[1] || ''
+ : (loc ? getPathFromRoot(href) : href || '')[REPLACE](base, '')
+}
+
+function emit(force) {
+ // the stack is needed for redirections
+ var isRoot = emitStackLevel == 0, first
+ if (MAX_EMIT_STACK_LEVEL <= emitStackLevel) return
+
+ emitStackLevel++
+ emitStack.push(function() {
+ var path = getPathFromBase()
+ if (force || path != current) {
+ central[TRIGGER]('emit', path)
+ current = path
+ }
+ })
+ if (isRoot) {
+ while (first = emitStack.shift()) first() // stack increses within this call
+ emitStackLevel = 0
+ }
+}
+
+function click(e) {
+ if (
+ e.which != 1 // not left click
+ || e.metaKey || e.ctrlKey || e.shiftKey // or meta keys
+ || e.defaultPrevented // or default prevented
+ ) return
+
+ var el = e.target
+ while (el && el.nodeName != 'A') el = el.parentNode
+
+ if (
+ !el || el.nodeName != 'A' // not A tag
+ || el[HAS_ATTRIBUTE]('download') // has download attr
+ || !el[HAS_ATTRIBUTE]('href') // has no href attr
+ || el.target && el.target != '_self' // another window or frame
+ || el.href.indexOf(loc.href.match(RE_ORIGIN)[0]) == -1 // cross origin
+ ) return
+
+ if (el.href != loc.href
+ && (
+ el.href.split('#')[0] == loc.href.split('#')[0] // internal jump
+ || base[0] != '#' && getPathFromRoot(el.href).indexOf(base) !== 0 // outside of base
+ || base[0] == '#' && el.href.split(base)[0] != loc.href.split(base)[0] // outside of #base
+ || !go(getPathFromBase(el.href), el.title || doc.title) // route not found
+ )) return
+
+ e.preventDefault()
+}
+
+/**
+ * Go to the path
+ * @param {string} path - destination path
+ * @param {string} title - page title
+ * @param {boolean} shouldReplace - use replaceState or pushState
+ * @returns {boolean} - route not found flag
+ */
+function go(path, title, shouldReplace) {
+ // Server-side usage: directly execute handlers for the path
+ if (!hist) return central[TRIGGER]('emit', getPathFromBase(path))
+
+ path = base + normalize(path)
+ title = title || doc.title
+ // browsers ignores the second parameter `title`
+ shouldReplace
+ ? hist.replaceState(null, title, path)
+ : hist.pushState(null, title, path)
+ // so we need to set it manually
+ doc.title = title
+ routeFound = false
+ emit()
+ return routeFound
+}
+
+/**
+ * Go to path or set action
+ * a single string: go there
+ * two strings: go there with setting a title
+ * two strings and boolean: replace history with setting a title
+ * a single function: set an action on the default route
+ * a string/RegExp and a function: set an action on the route
+ * @param {(string|function)} first - path / action / filter
+ * @param {(string|RegExp|function)} second - title / action
+ * @param {boolean} third - replace flag
+ */
+prot.m = function(first, second, third) {
+ if (isString(first) && (!second || isString(second))) go(first, second, third || false)
+ else if (second) this.r(first, second)
+ else this.r('@', first)
+}
+
+/**
+ * Stop routing
+ */
+prot.s = function() {
+ this.off('*')
+ this.$ = []
+}
+
+/**
+ * Emit
+ * @param {string} path - path
+ */
+prot.e = function(path) {
+ this.$.concat('@').some(function(filter) {
+ var args = (filter == '@' ? parser : secondParser)(normalize(path), normalize(filter))
+ if (typeof args != 'undefined') {
+ this[TRIGGER].apply(null, [filter].concat(args))
+ return routeFound = true // exit from loop
+ }
+ }, this)
+}
+
+/**
+ * Register route
+ * @param {string} filter - filter for matching to url
+ * @param {function} action - action to register
+ */
+prot.r = function(filter, action) {
+ if (filter != '@') {
+ filter = '/' + normalize(filter)
+ this.$.push(filter)
+ }
+ this.on(filter, action)
+}
+
+var mainRouter = new Router()
+var route = mainRouter.m.bind(mainRouter)
+
+/**
+ * Create a sub router
+ * @returns {function} the method of a new Router object
+ */
+route.create = function() {
+ var newSubRouter = new Router()
+ // assign sub-router's main method
+ var router = newSubRouter.m.bind(newSubRouter)
+ // stop only this sub-router
+ router.stop = newSubRouter.s.bind(newSubRouter)
+ return router
+}
+
+/**
+ * Set the base of url
+ * @param {(str|RegExp)} arg - a new base or '#' or '#!'
+ */
+route.base = function(arg) {
+ base = arg || '#'
+ current = getPathFromBase() // recalculate current path
+}
+
+/** Exec routing right now **/
+route.exec = function() {
+ emit(true)
+}
+
+/**
+ * Replace the default router to yours
+ * @param {function} fn - your parser function
+ * @param {function} fn2 - your secondParser function
+ */
+route.parser = function(fn, fn2) {
+ if (!fn && !fn2) {
+ // reset parser for testing...
+ parser = DEFAULT_PARSER
+ secondParser = DEFAULT_SECOND_PARSER
+ }
+ if (fn) parser = fn
+ if (fn2) secondParser = fn2
+}
+
+/**
+ * Helper function to get url query as an object
+ * @returns {object} parsed query
+ */
+route.query = function() {
+ var q = {}
+ var href = loc.href || current
+ href[REPLACE](/[?&](.+?)=([^&]*)/g, function(_, k, v) { q[k] = v })
+ return q
+}
+
+/** Stop routing **/
+route.stop = function () {
+ if (started) {
+ if (win) {
+ win[REMOVE_EVENT_LISTENER](POPSTATE, debouncedEmit)
+ win[REMOVE_EVENT_LISTENER](HASHCHANGE, debouncedEmit)
+ doc[REMOVE_EVENT_LISTENER](clickEvent, click)
+ }
+ central[TRIGGER]('stop')
+ started = false
+ }
+}
+
+/**
+ * Start routing
+ * @param {boolean} autoExec - automatically exec after starting if true
+ */
+route.start = function (autoExec) {
+ if (!started) {
+ if (win) {
+ if (document.readyState == 'complete') start(autoExec)
+ // the timeout is needed to solve
+ // a weird safari bug https://github.com/riot/route/issues/33
+ else win[ADD_EVENT_LISTENER]('load', function() {
+ setTimeout(function() { start(autoExec) }, 1)
+ })
+ }
+ started = true
+ }
+}
+
+/** Prepare the router **/
+route.base()
+route.parser()
+
+riot.route = route
+})(riot)
+/* istanbul ignore next */
+
+/**
+ * The riot template engine
+ * @version v2.4.1
+ */
+/**
+ * riot.util.brackets
+ *
+ * - `brackets ` - Returns a string or regex based on its parameter
+ * - `brackets.set` - Change the current riot brackets
+ *
+ * @module
+ */
+
+var brackets = (function (UNDEF) {
+
+ var
+ REGLOB = 'g',
+
+ R_MLCOMMS = /\/\*[^*]*\*+(?:[^*\/][^*]*\*+)*\//g,
+
+ R_STRINGS = /"[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'/g,
+
+ S_QBLOCKS = R_STRINGS.source + '|' +
+ /(?:\breturn\s+|(?:[$\w\)\]]|\+\+|--)\s*(\/)(?![*\/]))/.source + '|' +
+ /\/(?=[^*\/])[^[\/\\]*(?:(?:\[(?:\\.|[^\]\\]*)*\]|\\.)[^[\/\\]*)*?(\/)[gim]*/.source,
+
+ UNSUPPORTED = RegExp('[\\' + 'x00-\\x1F<>a-zA-Z0-9\'",;\\\\]'),
+
+ NEED_ESCAPE = /(?=[[\]()*+?.^$|])/g,
+
+ FINDBRACES = {
+ '(': RegExp('([()])|' + S_QBLOCKS, REGLOB),
+ '[': RegExp('([[\\]])|' + S_QBLOCKS, REGLOB),
+ '{': RegExp('([{}])|' + S_QBLOCKS, REGLOB)
+ },
+
+ DEFAULT = '{ }'
+
+ var _pairs = [
+ '{', '}',
+ '{', '}',
+ /{[^}]*}/,
+ /\\([{}])/g,
+ /\\({)|{/g,
+ RegExp('\\\\(})|([[({])|(})|' + S_QBLOCKS, REGLOB),
+ DEFAULT,
+ /^\s*{\^?\s*([$\w]+)(?:\s*,\s*(\S+))?\s+in\s+(\S.*)\s*}/,
+ /(^|[^\\]){=[\S\s]*?}/
+ ]
+
+ var
+ cachedBrackets = UNDEF,
+ _regex,
+ _cache = [],
+ _settings
+
+ function _loopback (re) { return re }
+
+ function _rewrite (re, bp) {
+ if (!bp) bp = _cache
+ return new RegExp(
+ re.source.replace(/{/g, bp[2]).replace(/}/g, bp[3]), re.global ? REGLOB : ''
+ )
+ }
+
+ function _create (pair) {
+ if (pair === DEFAULT) return _pairs
+
+ var arr = pair.split(' ')
+
+ if (arr.length !== 2 || UNSUPPORTED.test(pair)) {
+ throw new Error('Unsupported brackets "' + pair + '"')
+ }
+ arr = arr.concat(pair.replace(NEED_ESCAPE, '\\').split(' '))
+
+ arr[4] = _rewrite(arr[1].length > 1 ? /{[\S\s]*?}/ : _pairs[4], arr)
+ arr[5] = _rewrite(pair.length > 3 ? /\\({|})/g : _pairs[5], arr)
+ arr[6] = _rewrite(_pairs[6], arr)
+ arr[7] = RegExp('\\\\(' + arr[3] + ')|([[({])|(' + arr[3] + ')|' + S_QBLOCKS, REGLOB)
+ arr[8] = pair
+ return arr
+ }
+
+ function _brackets (reOrIdx) {
+ return reOrIdx instanceof RegExp ? _regex(reOrIdx) : _cache[reOrIdx]
+ }
+
+ _brackets.split = function split (str, tmpl, _bp) {
+ // istanbul ignore next: _bp is for the compiler
+ if (!_bp) _bp = _cache
+
+ var
+ parts = [],
+ match,
+ isexpr,
+ start,
+ pos,
+ re = _bp[6]
+
+ isexpr = start = re.lastIndex = 0
+
+ while ((match = re.exec(str))) {
+
+ pos = match.index
+
+ if (isexpr) {
+
+ if (match[2]) {
+ re.lastIndex = skipBraces(str, match[2], re.lastIndex)
+ continue
+ }
+ if (!match[3]) {
+ continue
+ }
+ }
+
+ if (!match[1]) {
+ unescapeStr(str.slice(start, pos))
+ start = re.lastIndex
+ re = _bp[6 + (isexpr ^= 1)]
+ re.lastIndex = start
+ }
+ }
+
+ if (str && start < str.length) {
+ unescapeStr(str.slice(start))
+ }
+
+ return parts
+
+ function unescapeStr (s) {
+ if (tmpl || isexpr) {
+ parts.push(s && s.replace(_bp[5], '$1'))
+ } else {
+ parts.push(s)
+ }
+ }
+
+ function skipBraces (s, ch, ix) {
+ var
+ match,
+ recch = FINDBRACES[ch]
+
+ recch.lastIndex = ix
+ ix = 1
+ while ((match = recch.exec(s))) {
+ if (match[1] &&
+ !(match[1] === ch ? ++ix : --ix)) break
+ }
+ return ix ? s.length : recch.lastIndex
+ }
+ }
+
+ _brackets.hasExpr = function hasExpr (str) {
+ return _cache[4].test(str)
+ }
+
+ _brackets.loopKeys = function loopKeys (expr) {
+ var m = expr.match(_cache[9])
+
+ return m
+ ? { key: m[1], pos: m[2], val: _cache[0] + m[3].trim() + _cache[1] }
+ : { val: expr.trim() }
+ }
+
+ _brackets.array = function array (pair) {
+ return pair ? _create(pair) : _cache
+ }
+
+ function _reset (pair) {
+ if ((pair || (pair = DEFAULT)) !== _cache[8]) {
+ _cache = _create(pair)
+ _regex = pair === DEFAULT ? _loopback : _rewrite
+ _cache[9] = _regex(_pairs[9])
+ }
+ cachedBrackets = pair
+ }
+
+ function _setSettings (o) {
+ var b
+
+ o = o || {}
+ b = o.brackets
+ Object.defineProperty(o, 'brackets', {
+ set: _reset,
+ get: function () { return cachedBrackets },
+ enumerable: true
+ })
+ _settings = o
+ _reset(b)
+ }
+
+ Object.defineProperty(_brackets, 'settings', {
+ set: _setSettings,
+ get: function () { return _settings }
+ })
+
+ /* istanbul ignore next: in the browser riot is always in the scope */
+ _brackets.settings = typeof riot !== 'undefined' && riot.settings || {}
+ _brackets.set = _reset
+
+ _brackets.R_STRINGS = R_STRINGS
+ _brackets.R_MLCOMMS = R_MLCOMMS
+ _brackets.S_QBLOCKS = S_QBLOCKS
+
+ return _brackets
+
+})()
+
+/**
+ * @module tmpl
+ *
+ * tmpl - Root function, returns the template value, render with data
+ * tmpl.hasExpr - Test the existence of a expression inside a string
+ * tmpl.loopKeys - Get the keys for an 'each' loop (used by `_each`)
+ */
+
+var tmpl = (function () {
+
+ var _cache = {}
+
+ function _tmpl (str, data) {
+ if (!str) return str
+
+ return (_cache[str] || (_cache[str] = _create(str))).call(data, _logErr)
+ }
+
+ _tmpl.haveRaw = brackets.hasRaw
+
+ _tmpl.hasExpr = brackets.hasExpr
+
+ _tmpl.loopKeys = brackets.loopKeys
+
+ // istanbul ignore next
+ _tmpl.clearCache = function () { _cache = {} }
+
+ _tmpl.errorHandler = null
+
+ function _logErr (err, ctx) {
+
+ if (_tmpl.errorHandler) {
+
+ err.riotData = {
+ tagName: ctx && ctx.root && ctx.root.tagName,
+ _riot_id: ctx && ctx._riot_id //eslint-disable-line camelcase
+ }
+ _tmpl.errorHandler(err)
+ }
+ }
+
+ function _create (str) {
+ var expr = _getTmpl(str)
+
+ if (expr.slice(0, 11) !== 'try{return ') expr = 'return ' + expr
+
+ return new Function('E', expr + ';') // eslint-disable-line no-new-func
+ }
+
+ var
+ CH_IDEXPR = '\u2057',
+ RE_CSNAME = /^(?:(-?[_A-Za-z\xA0-\xFF][-\w\xA0-\xFF]*)|\u2057(\d+)~):/,
+ RE_QBLOCK = RegExp(brackets.S_QBLOCKS, 'g'),
+ RE_DQUOTE = /\u2057/g,
+ RE_QBMARK = /\u2057(\d+)~/g
+
+ function _getTmpl (str) {
+ var
+ qstr = [],
+ expr,
+ parts = brackets.split(str.replace(RE_DQUOTE, '"'), 1)
+
+ if (parts.length > 2 || parts[0]) {
+ var i, j, list = []
+
+ for (i = j = 0; i < parts.length; ++i) {
+
+ expr = parts[i]
+
+ if (expr && (expr = i & 1
+
+ ? _parseExpr(expr, 1, qstr)
+
+ : '"' + expr
+ .replace(/\\/g, '\\\\')
+ .replace(/\r\n?|\n/g, '\\n')
+ .replace(/"/g, '\\"') +
+ '"'
+
+ )) list[j++] = expr
+
+ }
+
+ expr = j < 2 ? list[0]
+ : '[' + list.join(',') + '].join("")'
+
+ } else {
+
+ expr = _parseExpr(parts[1], 0, qstr)
+ }
+
+ if (qstr[0]) {
+ expr = expr.replace(RE_QBMARK, function (_, pos) {
+ return qstr[pos]
+ .replace(/\r/g, '\\r')
+ .replace(/\n/g, '\\n')
+ })
+ }
+ return expr
+ }
+
+ var
+ RE_BREND = {
+ '(': /[()]/g,
+ '[': /[[\]]/g,
+ '{': /[{}]/g
+ }
+
+ function _parseExpr (expr, asText, qstr) {
+
+ expr = expr
+ .replace(RE_QBLOCK, function (s, div) {
+ return s.length > 2 && !div ? CH_IDEXPR + (qstr.push(s) - 1) + '~' : s
+ })
+ .replace(/\s+/g, ' ').trim()
+ .replace(/\ ?([[\({},?\.:])\ ?/g, '$1')
+
+ if (expr) {
+ var
+ list = [],
+ cnt = 0,
+ match
+
+ while (expr &&
+ (match = expr.match(RE_CSNAME)) &&
+ !match.index
+ ) {
+ var
+ key,
+ jsb,
+ re = /,|([[{(])|$/g
+
+ expr = RegExp.rightContext
+ key = match[2] ? qstr[match[2]].slice(1, -1).trim().replace(/\s+/g, ' ') : match[1]
+
+ while (jsb = (match = re.exec(expr))[1]) skipBraces(jsb, re)
+
+ jsb = expr.slice(0, match.index)
+ expr = RegExp.rightContext
+
+ list[cnt++] = _wrapExpr(jsb, 1, key)
+ }
+
+ expr = !cnt ? _wrapExpr(expr, asText)
+ : cnt > 1 ? '[' + list.join(',') + '].join(" ").trim()' : list[0]
+ }
+ return expr
+
+ function skipBraces (ch, re) {
+ var
+ mm,
+ lv = 1,
+ ir = RE_BREND[ch]
+
+ ir.lastIndex = re.lastIndex
+ while (mm = ir.exec(expr)) {
+ if (mm[0] === ch) ++lv
+ else if (!--lv) break
+ }
+ re.lastIndex = lv ? expr.length : ir.lastIndex
+ }
+ }
+
+ // istanbul ignore next: not both
+ var // eslint-disable-next-line max-len
+ JS_CONTEXT = '"in this?this:' + (typeof window !== 'object' ? 'global' : 'window') + ').',
+ JS_VARNAME = /[,{][$\w]+(?=:)|(^ *|[^$\w\.])(?!(?:typeof|true|false|null|undefined|in|instanceof|is(?:Finite|NaN)|void|NaN|new|Date|RegExp|Math)(?![$\w]))([$_A-Za-z][$\w]*)/g,
+ JS_NOPROPS = /^(?=(\.[$\w]+))\1(?:[^.[(]|$)/
+
+ function _wrapExpr (expr, asText, key) {
+ var tb
+
+ expr = expr.replace(JS_VARNAME, function (match, p, mvar, pos, s) {
+ if (mvar) {
+ pos = tb ? 0 : pos + match.length
+
+ if (mvar !== 'this' && mvar !== 'global' && mvar !== 'window') {
+ match = p + '("' + mvar + JS_CONTEXT + mvar
+ if (pos) tb = (s = s[pos]) === '.' || s === '(' || s === '['
+ } else if (pos) {
+ tb = !JS_NOPROPS.test(s.slice(pos))
+ }
+ }
+ return match
+ })
+
+ if (tb) {
+ expr = 'try{return ' + expr + '}catch(e){E(e,this)}'
+ }
+
+ if (key) {
+
+ expr = (tb
+ ? 'function(){' + expr + '}.call(this)' : '(' + expr + ')'
+ ) + '?"' + key + '":""'
+
+ } else if (asText) {
+
+ expr = 'function(v){' + (tb
+ ? expr.replace('return ', 'v=') : 'v=(' + expr + ')'
+ ) + ';return v||v===0?v:""}.call(this)'
+ }
+
+ return expr
+ }
+
+ _tmpl.version = brackets.version = 'v2.4.1'
+
+ return _tmpl
+
+})()
+
+/*
+ lib/browser/tag/mkdom.js
+
+ Includes hacks needed for the Internet Explorer version 9 and below
+ See: http://kangax.github.io/compat-table/es5/#ie8
+ http://codeplanet.io/dropping-ie8/
+*/
+var mkdom = (function _mkdom() {
+ var
+ reHasYield = /|>([\S\s]*?)<\/yield\s*>|>)/ig,
+ reYieldSrc = /]*)['"]\s*>([\S\s]*?)<\/yield\s*>/ig,
+ reYieldDest = / |>([\S\s]*?)<\/yield\s*>)/ig
+ var
+ rootEls = { tr: 'tbody', th: 'tr', td: 'tr', col: 'colgroup' },
+ tblTags = IE_VERSION && IE_VERSION < 10
+ ? SPECIAL_TAGS_REGEX : /^(?:t(?:body|head|foot|[rhd])|caption|col(?:group)?)$/
+
+ /**
+ * Creates a DOM element to wrap the given content. Normally an `DIV`, but can be
+ * also a `TABLE`, `SELECT`, `TBODY`, `TR`, or `COLGROUP` element.
+ *
+ * @param {string} templ - The template coming from the custom tag definition
+ * @param {string} [html] - HTML content that comes from the DOM element where you
+ * will mount the tag, mostly the original tag in the page
+ * @returns {HTMLElement} DOM element with _templ_ merged through `YIELD` with the _html_.
+ */
+ function _mkdom(templ, html) {
+ var
+ match = templ && templ.match(/^\s*<([-\w]+)/),
+ tagName = match && match[1].toLowerCase(),
+ el = mkEl('div', isSVGTag(tagName))
+
+ // replace all the yield tags with the tag inner html
+ templ = replaceYield(templ, html)
+
+ /* istanbul ignore next */
+ if (tblTags.test(tagName))
+ el = specialTags(el, templ, tagName)
+ else
+ setInnerHTML(el, templ)
+
+ el.stub = true
+
+ return el
+ }
+
+ /*
+ Creates the root element for table or select child elements:
+ tr/th/td/thead/tfoot/tbody/caption/col/colgroup/option/optgroup
+ */
+ function specialTags(el, templ, tagName) {
+ var
+ select = tagName[0] === 'o',
+ parent = select ? 'select>' : 'table>'
+
+ // trim() is important here, this ensures we don't have artifacts,
+ // so we can check if we have only one element inside the parent
+ el.innerHTML = '<' + parent + templ.trim() + '' + parent
+ parent = el.firstChild
+
+ // returns the immediate parent if tr/th/td/col is the only element, if not
+ // returns the whole tree, as this can include additional elements
+ if (select) {
+ parent.selectedIndex = -1 // for IE9, compatible w/current riot behavior
+ } else {
+ // avoids insertion of cointainer inside container (ex: tbody inside tbody)
+ var tname = rootEls[tagName]
+ if (tname && parent.childElementCount === 1) parent = $(tname, parent)
+ }
+ return parent
+ }
+
+ /*
+ Replace the yield tag from any tag template with the innerHTML of the
+ original tag in the page
+ */
+ function replaceYield(templ, html) {
+ // do nothing if no yield
+ if (!reHasYield.test(templ)) return templ
+
+ // be careful with #1343 - string on the source having `$1`
+ var src = {}
+
+ html = html && html.replace(reYieldSrc, function (_, ref, text) {
+ src[ref] = src[ref] || text // preserve first definition
+ return ''
+ }).trim()
+
+ return templ
+ .replace(reYieldDest, function (_, ref, def) { // yield with from - to attrs
+ return src[ref] || def || ''
+ })
+ .replace(reYieldAll, function (_, def) { // yield without any "from"
+ return html || def || ''
+ })
+ }
+
+ return _mkdom
+
+})()
+
+/**
+ * Convert the item looped into an object used to extend the child tag properties
+ * @param { Object } expr - object containing the keys used to extend the children tags
+ * @param { * } key - value to assign to the new object returned
+ * @param { * } val - value containing the position of the item in the array
+ * @returns { Object } - new object containing the values of the original item
+ *
+ * The variables 'key' and 'val' are arbitrary.
+ * They depend on the collection type looped (Array, Object)
+ * and on the expression used on the each tag
+ *
+ */
+function mkitem(expr, key, val) {
+ var item = {}
+ item[expr.key] = key
+ if (expr.pos) item[expr.pos] = val
+ return item
+}
+
+/**
+ * Unmount the redundant tags
+ * @param { Array } items - array containing the current items to loop
+ * @param { Array } tags - array containing all the children tags
+ */
+function unmountRedundant(items, tags) {
+
+ var i = tags.length,
+ j = items.length,
+ t
+
+ while (i > j) {
+ t = tags[--i]
+ tags.splice(i, 1)
+ t.unmount()
+ }
+}
+
+/**
+ * Move the nested custom tags in non custom loop tags
+ * @param { Object } child - non custom loop tag
+ * @param { Number } i - current position of the loop tag
+ */
+function moveNestedTags(child, i) {
+ Object.keys(child.tags).forEach(function(tagName) {
+ var tag = child.tags[tagName]
+ if (isArray(tag))
+ each(tag, function (t) {
+ moveChildTag(t, tagName, i)
+ })
+ else
+ moveChildTag(tag, tagName, i)
+ })
+}
+
+/**
+ * Adds the elements for a virtual tag
+ * @param { Tag } tag - the tag whose root's children will be inserted or appended
+ * @param { Node } src - the node that will do the inserting or appending
+ * @param { Tag } target - only if inserting, insert before this tag's first child
+ */
+function addVirtual(tag, src, target) {
+ var el = tag._root, sib
+ tag._virts = []
+ while (el) {
+ sib = el.nextSibling
+ if (target)
+ src.insertBefore(el, target._root)
+ else
+ src.appendChild(el)
+
+ tag._virts.push(el) // hold for unmounting
+ el = sib
+ }
+}
+
+/**
+ * Move virtual tag and all child nodes
+ * @param { Tag } tag - first child reference used to start move
+ * @param { Node } src - the node that will do the inserting
+ * @param { Tag } target - insert before this tag's first child
+ * @param { Number } len - how many child nodes to move
+ */
+function moveVirtual(tag, src, target, len) {
+ var el = tag._root, sib, i = 0
+ for (; i < len; i++) {
+ sib = el.nextSibling
+ src.insertBefore(el, target._root)
+ el = sib
+ }
+}
+
+
+/**
+ * Manage tags having the 'each'
+ * @param { Object } dom - DOM node we need to loop
+ * @param { Tag } parent - parent tag instance where the dom node is contained
+ * @param { String } expr - string contained in the 'each' attribute
+ */
+function _each(dom, parent, expr) {
+
+ // remove the each property from the original tag
+ remAttr(dom, 'each')
+
+ var mustReorder = typeof getAttr(dom, 'no-reorder') !== T_STRING || remAttr(dom, 'no-reorder'),
+ tagName = getTagName(dom),
+ impl = __tagImpl[tagName] || { tmpl: getOuterHTML(dom) },
+ useRoot = SPECIAL_TAGS_REGEX.test(tagName),
+ root = dom.parentNode,
+ ref = document.createTextNode(''),
+ child = getTag(dom),
+ isOption = tagName.toLowerCase() === 'option', // the option tags must be treated differently
+ tags = [],
+ oldItems = [],
+ hasKeys,
+ isVirtual = dom.tagName == 'VIRTUAL'
+
+ // parse the each expression
+ expr = tmpl.loopKeys(expr)
+
+ // insert a marked where the loop tags will be injected
+ root.insertBefore(ref, dom)
+
+ // clean template code
+ parent.one('before-mount', function () {
+
+ // remove the original DOM node
+ dom.parentNode.removeChild(dom)
+ if (root.stub) root = parent.root
+
+ }).on('update', function () {
+ // get the new items collection
+ var items = tmpl(expr.val, parent),
+ // create a fragment to hold the new DOM nodes to inject in the parent tag
+ frag = document.createDocumentFragment()
+
+ // object loop. any changes cause full redraw
+ if (!isArray(items)) {
+ hasKeys = items || false
+ items = hasKeys ?
+ Object.keys(items).map(function (key) {
+ return mkitem(expr, key, items[key])
+ }) : []
+ }
+
+ // loop all the new items
+ var i = 0,
+ itemsLength = items.length
+
+ for (; i < itemsLength; i++) {
+ // reorder only if the items are objects
+ var
+ item = items[i],
+ _mustReorder = mustReorder && typeof item == T_OBJECT && !hasKeys,
+ oldPos = oldItems.indexOf(item),
+ pos = ~oldPos && _mustReorder ? oldPos : i,
+ // does a tag exist in this position?
+ tag = tags[pos]
+
+ item = !hasKeys && expr.key ? mkitem(expr, item, i) : item
+
+ // new tag
+ if (
+ !_mustReorder && !tag // with no-reorder we just update the old tags
+ ||
+ _mustReorder && !~oldPos || !tag // by default we always try to reorder the DOM elements
+ ) {
+
+ tag = new Tag(impl, {
+ parent: parent,
+ isLoop: true,
+ hasImpl: !!__tagImpl[tagName],
+ root: useRoot ? root : dom.cloneNode(),
+ item: item
+ }, dom.innerHTML)
+
+ tag.mount()
+
+ if (isVirtual) tag._root = tag.root.firstChild // save reference for further moves or inserts
+ // this tag must be appended
+ if (i == tags.length || !tags[i]) { // fix 1581
+ if (isVirtual)
+ addVirtual(tag, frag)
+ else frag.appendChild(tag.root)
+ }
+ // this tag must be insert
+ else {
+ if (isVirtual)
+ addVirtual(tag, root, tags[i])
+ else root.insertBefore(tag.root, tags[i].root) // #1374 some browsers reset selected here
+ oldItems.splice(i, 0, item)
+ }
+
+ tags.splice(i, 0, tag)
+ pos = i // handled here so no move
+ } else tag.update(item, true)
+
+ // reorder the tag if it's not located in its previous position
+ if (
+ pos !== i && _mustReorder &&
+ tags[i] // fix 1581 unable to reproduce it in a test!
+ ) {
+ // update the DOM
+ if (isVirtual)
+ moveVirtual(tag, root, tags[i], dom.childNodes.length)
+ else if (tags[i].root.parentNode) root.insertBefore(tag.root, tags[i].root)
+ // update the position attribute if it exists
+ if (expr.pos)
+ tag[expr.pos] = i
+ // move the old tag instance
+ tags.splice(i, 0, tags.splice(pos, 1)[0])
+ // move the old item
+ oldItems.splice(i, 0, oldItems.splice(pos, 1)[0])
+ // if the loop tags are not custom
+ // we need to move all their custom tags into the right position
+ if (!child && tag.tags) moveNestedTags(tag, i)
+ }
+
+ // cache the original item to use it in the events bound to this node
+ // and its children
+ tag._item = item
+ // cache the real parent tag internally
+ defineProperty(tag, '_parent', parent)
+ }
+
+ // remove the redundant tags
+ unmountRedundant(items, tags)
+
+ // insert the new nodes
+ root.insertBefore(frag, ref)
+ if (isOption) {
+
+ // #1374 FireFox bug in
+ if (FIREFOX && !root.multiple) {
+ for (var n = 0; n < root.length; n++) {
+ if (root[n].__riot1374) {
+ root.selectedIndex = n // clear other options
+ delete root[n].__riot1374
+ break
+ }
+ }
+ }
+ }
+
+ // set the 'tags' property of the parent tag
+ // if child is 'undefined' it means that we don't need to set this property
+ // for example:
+ // we don't need store the `myTag.tags['div']` property if we are looping a div tag
+ // but we need to track the `myTag.tags['child']` property looping a custom child node named `child`
+ if (child) parent.tags[tagName] = tags
+
+ // clone the items array
+ oldItems = items.slice()
+
+ })
+
+}
+/**
+ * Object that will be used to inject and manage the css of every tag instance
+ */
+var styleManager = (function(_riot) {
+
+ if (!window) return { // skip injection on the server
+ add: function () {},
+ inject: function () {}
+ }
+
+ var styleNode = (function () {
+ // create a new style element with the correct type
+ var newNode = mkEl('style')
+ setAttr(newNode, 'type', 'text/css')
+
+ // replace any user node or insert the new one into the head
+ var userNode = $('style[type=riot]')
+ if (userNode) {
+ if (userNode.id) newNode.id = userNode.id
+ userNode.parentNode.replaceChild(newNode, userNode)
+ }
+ else document.getElementsByTagName('head')[0].appendChild(newNode)
+
+ return newNode
+ })()
+
+ // Create cache and shortcut to the correct property
+ var cssTextProp = styleNode.styleSheet,
+ stylesToInject = ''
+
+ // Expose the style node in a non-modificable property
+ Object.defineProperty(_riot, 'styleNode', {
+ value: styleNode,
+ writable: true
+ })
+
+ /**
+ * Public api
+ */
+ return {
+ /**
+ * Save a tag style to be later injected into DOM
+ * @param { String } css [description]
+ */
+ add: function(css) {
+ stylesToInject += css
+ },
+ /**
+ * Inject all previously saved tag styles into DOM
+ * innerHTML seems slow: http://jsperf.com/riot-insert-style
+ */
+ inject: function() {
+ if (stylesToInject) {
+ if (cssTextProp) cssTextProp.cssText += stylesToInject
+ else styleNode.innerHTML += stylesToInject
+ stylesToInject = ''
+ }
+ }
+ }
+
+})(riot)
+
+
+function parseNamedElements(root, tag, childTags, forceParsingNamed) {
+
+ walk(root, function(dom) {
+ if (dom.nodeType == 1) {
+ dom.isLoop = dom.isLoop ||
+ (dom.parentNode && dom.parentNode.isLoop || getAttr(dom, 'each'))
+ ? 1 : 0
+
+ // custom child tag
+ if (childTags) {
+ var child = getTag(dom)
+
+ if (child && !dom.isLoop)
+ childTags.push(initChildTag(child, {root: dom, parent: tag}, dom.innerHTML, tag))
+ }
+
+ if (!dom.isLoop || forceParsingNamed)
+ setNamed(dom, tag, [])
+ }
+
+ })
+
+}
+
+function parseExpressions(root, tag, expressions) {
+
+ function addExpr(dom, val, extra) {
+ if (tmpl.hasExpr(val)) {
+ expressions.push(extend({ dom: dom, expr: val }, extra))
+ }
+ }
+
+ walk(root, function(dom) {
+ var type = dom.nodeType,
+ attr
+
+ // text node
+ if (type == 3 && dom.parentNode.tagName != 'STYLE') addExpr(dom, dom.nodeValue)
+ if (type != 1) return
+
+ /* element */
+
+ // loop
+ attr = getAttr(dom, 'each')
+
+ if (attr) { _each(dom, tag, attr); return false }
+
+ // attribute expressions
+ each(dom.attributes, function(attr) {
+ var name = attr.name,
+ bool = name.split('__')[1]
+
+ addExpr(dom, attr.value, { attr: bool || name, bool: bool })
+ if (bool) { remAttr(dom, name); return false }
+
+ })
+
+ // skip custom tags
+ if (getTag(dom)) return false
+
+ })
+
+}
+function Tag(impl, conf, innerHTML) {
+
+ var self = riot.observable(this),
+ opts = inherit(conf.opts) || {},
+ parent = conf.parent,
+ isLoop = conf.isLoop,
+ hasImpl = conf.hasImpl,
+ item = cleanUpData(conf.item),
+ expressions = [],
+ childTags = [],
+ root = conf.root,
+ tagName = root.tagName.toLowerCase(),
+ attr = {},
+ propsInSyncWithParent = [],
+ dom
+
+ // only call unmount if we have a valid __tagImpl (has name property)
+ if (impl.name && root._tag) root._tag.unmount(true)
+
+ // not yet mounted
+ this.isMounted = false
+ root.isLoop = isLoop
+
+ // keep a reference to the tag just created
+ // so we will be able to mount this tag multiple times
+ root._tag = this
+
+ // create a unique id to this tag
+ // it could be handy to use it also to improve the virtual dom rendering speed
+ defineProperty(this, '_riot_id', ++__uid) // base 1 allows test !t._riot_id
+
+ extend(this, { parent: parent, root: root, opts: opts}, item)
+ // protect the "tags" property from being overridden
+ defineProperty(this, 'tags', {})
+
+ // grab attributes
+ each(root.attributes, function(el) {
+ var val = el.value
+ // remember attributes with expressions only
+ if (tmpl.hasExpr(val)) attr[el.name] = val
+ })
+
+ dom = mkdom(impl.tmpl, innerHTML)
+
+ // options
+ function updateOpts() {
+ var ctx = hasImpl && isLoop ? self : parent || self
+
+ // update opts from current DOM attributes
+ each(root.attributes, function(el) {
+ var val = el.value
+ opts[toCamel(el.name)] = tmpl.hasExpr(val) ? tmpl(val, ctx) : val
+ })
+ // recover those with expressions
+ each(Object.keys(attr), function(name) {
+ opts[toCamel(name)] = tmpl(attr[name], ctx)
+ })
+ }
+
+ function normalizeData(data) {
+ for (var key in item) {
+ if (typeof self[key] !== T_UNDEF && isWritable(self, key))
+ self[key] = data[key]
+ }
+ }
+
+ function inheritFrom(target) {
+ each(Object.keys(target), function(k) {
+ // some properties must be always in sync with the parent tag
+ var mustSync = !RESERVED_WORDS_BLACKLIST.test(k) && contains(propsInSyncWithParent, k)
+
+ if (typeof self[k] === T_UNDEF || mustSync) {
+ // track the property to keep in sync
+ // so we can keep it updated
+ if (!mustSync) propsInSyncWithParent.push(k)
+ self[k] = target[k]
+ }
+ })
+ }
+
+ /**
+ * Update the tag expressions and options
+ * @param { * } data - data we want to use to extend the tag properties
+ * @param { Boolean } isInherited - is this update coming from a parent tag?
+ * @returns { self }
+ */
+ defineProperty(this, 'update', function(data, isInherited) {
+
+ // make sure the data passed will not override
+ // the component core methods
+ data = cleanUpData(data)
+ // inherit properties from the parent in loop
+ if (isLoop) {
+ inheritFrom(self.parent)
+ }
+ // normalize the tag properties in case an item object was initially passed
+ if (data && isObject(item)) {
+ normalizeData(data)
+ item = data
+ }
+ extend(self, data)
+ updateOpts()
+ self.trigger('update', data)
+ update(expressions, self)
+
+ // the updated event will be triggered
+ // once the DOM will be ready and all the re-flows are completed
+ // this is useful if you want to get the "real" root properties
+ // 4 ex: root.offsetWidth ...
+ if (isInherited && self.parent)
+ // closes #1599
+ self.parent.one('updated', function() { self.trigger('updated') })
+ else rAF(function() { self.trigger('updated') })
+
+ return this
+ })
+
+ defineProperty(this, 'mixin', function() {
+ each(arguments, function(mix) {
+ var instance,
+ props = [],
+ obj
+
+ mix = typeof mix === T_STRING ? riot.mixin(mix) : mix
+
+ // check if the mixin is a function
+ if (isFunction(mix)) {
+ // create the new mixin instance
+ instance = new mix()
+ } else instance = mix
+
+ // build multilevel prototype inheritance chain property list
+ do props = props.concat(Object.getOwnPropertyNames(obj || instance))
+ while (obj = Object.getPrototypeOf(obj || instance))
+
+ // loop the keys in the function prototype or the all object keys
+ each(props, function(key) {
+ // bind methods to self
+ // allow mixins to override other properties/parent mixins
+ if (key != 'init') {
+ // check for getters/setters
+ var descriptor = Object.getOwnPropertyDescriptor(instance, key)
+ var hasGetterSetter = descriptor && (descriptor.get || descriptor.set)
+
+ // apply method only if it does not already exist on the instance
+ if (!self.hasOwnProperty(key) && hasGetterSetter) {
+ Object.defineProperty(self, key, descriptor)
+ } else {
+ self[key] = isFunction(instance[key]) ?
+ instance[key].bind(self) :
+ instance[key]
+ }
+ }
+ })
+
+ // init method will be called automatically
+ if (instance.init) instance.init.bind(self)()
+ })
+ return this
+ })
+
+ defineProperty(this, 'mount', function() {
+
+ updateOpts()
+
+ // add global mixins
+ var globalMixin = riot.mixin(GLOBAL_MIXIN)
+
+ if (globalMixin)
+ for (var i in globalMixin)
+ if (globalMixin.hasOwnProperty(i))
+ self.mixin(globalMixin[i])
+
+ // children in loop should inherit from true parent
+ if (self._parent) {
+ inheritFrom(self._parent)
+ }
+
+ // initialiation
+ if (impl.fn) impl.fn.call(self, opts)
+
+ // parse layout after init. fn may calculate args for nested custom tags
+ parseExpressions(dom, self, expressions)
+
+ // mount the child tags
+ toggle(true)
+
+ // update the root adding custom attributes coming from the compiler
+ // it fixes also #1087
+ if (impl.attrs)
+ walkAttributes(impl.attrs, function (k, v) { setAttr(root, k, v) })
+ if (impl.attrs || hasImpl)
+ parseExpressions(self.root, self, expressions)
+
+ if (!self.parent || isLoop) self.update(item)
+
+ // internal use only, fixes #403
+ self.trigger('before-mount')
+
+ if (isLoop && !hasImpl) {
+ // update the root attribute for the looped elements
+ root = dom.firstChild
+ } else {
+ while (dom.firstChild) root.appendChild(dom.firstChild)
+ if (root.stub) root = parent.root
+ }
+
+ defineProperty(self, 'root', root)
+
+ // parse the named dom nodes in the looped child
+ // adding them to the parent as well
+ if (isLoop)
+ parseNamedElements(self.root, self.parent, null, true)
+
+ // if it's not a child tag we can trigger its mount event
+ if (!self.parent || self.parent.isMounted) {
+ self.isMounted = true
+ self.trigger('mount')
+ }
+ // otherwise we need to wait that the parent event gets triggered
+ else self.parent.one('mount', function() {
+ // avoid to trigger the `mount` event for the tags
+ // not visible included in an if statement
+ if (!isInStub(self.root)) {
+ self.parent.isMounted = self.isMounted = true
+ self.trigger('mount')
+ }
+ })
+ })
+
+
+ defineProperty(this, 'unmount', function(keepRootTag) {
+ var el = root,
+ p = el.parentNode,
+ ptag,
+ tagIndex = __virtualDom.indexOf(self)
+
+ self.trigger('before-unmount')
+
+ // remove this tag instance from the global virtualDom variable
+ if (~tagIndex)
+ __virtualDom.splice(tagIndex, 1)
+
+ if (p) {
+
+ if (parent) {
+ ptag = getImmediateCustomParentTag(parent)
+ // remove this tag from the parent tags object
+ // if there are multiple nested tags with same name..
+ // remove this element form the array
+ if (isArray(ptag.tags[tagName]))
+ each(ptag.tags[tagName], function(tag, i) {
+ if (tag._riot_id == self._riot_id)
+ ptag.tags[tagName].splice(i, 1)
+ })
+ else
+ // otherwise just delete the tag instance
+ ptag.tags[tagName] = undefined
+ }
+
+ else
+ while (el.firstChild) el.removeChild(el.firstChild)
+
+ if (!keepRootTag)
+ p.removeChild(el)
+ else {
+ // the riot-tag and the data-is attributes aren't needed anymore, remove them
+ remAttr(p, RIOT_TAG_IS)
+ remAttr(p, RIOT_TAG) // this will be removed in riot 3.0.0
+ }
+
+ }
+
+ if (this._virts) {
+ each(this._virts, function(v) {
+ if (v.parentNode) v.parentNode.removeChild(v)
+ })
+ }
+
+ self.trigger('unmount')
+ toggle()
+ self.off('*')
+ self.isMounted = false
+ delete root._tag
+
+ })
+
+ // proxy function to bind updates
+ // dispatched from a parent tag
+ function onChildUpdate(data) { self.update(data, true) }
+
+ function toggle(isMount) {
+
+ // mount/unmount children
+ each(childTags, function(child) { child[isMount ? 'mount' : 'unmount']() })
+
+ // listen/unlisten parent (events flow one way from parent to children)
+ if (!parent) return
+ var evt = isMount ? 'on' : 'off'
+
+ // the loop tags will be always in sync with the parent automatically
+ if (isLoop)
+ parent[evt]('unmount', self.unmount)
+ else {
+ parent[evt]('update', onChildUpdate)[evt]('unmount', self.unmount)
+ }
+ }
+
+
+ // named elements available for fn
+ parseNamedElements(dom, this, childTags)
+
+}
+/**
+ * Attach an event to a DOM node
+ * @param { String } name - event name
+ * @param { Function } handler - event callback
+ * @param { Object } dom - dom node
+ * @param { Tag } tag - tag instance
+ */
+function setEventHandler(name, handler, dom, tag) {
+
+ dom[name] = function(e) {
+
+ var ptag = tag._parent,
+ item = tag._item,
+ el
+
+ if (!item)
+ while (ptag && !item) {
+ item = ptag._item
+ ptag = ptag._parent
+ }
+
+ // cross browser event fix
+ e = e || window.event
+
+ // override the event properties
+ if (isWritable(e, 'currentTarget')) e.currentTarget = dom
+ if (isWritable(e, 'target')) e.target = e.srcElement
+ if (isWritable(e, 'which')) e.which = e.charCode || e.keyCode
+
+ e.item = item
+
+ // prevent default behaviour (by default)
+ if (handler.call(tag, e) !== true && !/radio|check/.test(dom.type)) {
+ if (e.preventDefault) e.preventDefault()
+ e.returnValue = false
+ }
+
+ if (!e.preventUpdate) {
+ el = item ? getImmediateCustomParentTag(ptag) : tag
+ el.update()
+ }
+
+ }
+
+}
+
+
+/**
+ * Insert a DOM node replacing another one (used by if- attribute)
+ * @param { Object } root - parent node
+ * @param { Object } node - node replaced
+ * @param { Object } before - node added
+ */
+function insertTo(root, node, before) {
+ if (!root) return
+ root.insertBefore(before, node)
+ root.removeChild(node)
+}
+
+/**
+ * Update the expressions in a Tag instance
+ * @param { Array } expressions - expression that must be re evaluated
+ * @param { Tag } tag - tag instance
+ */
+function update(expressions, tag) {
+
+ each(expressions, function(expr, i) {
+
+ var dom = expr.dom,
+ attrName = expr.attr,
+ value = tmpl(expr.expr, tag),
+ parent = expr.parent || expr.dom.parentNode
+
+ if (expr.bool) {
+ value = !!value
+ } else if (value == null) {
+ value = ''
+ }
+
+ // #1638: regression of #1612, update the dom only if the value of the
+ // expression was changed
+ if (expr.value === value) {
+ return
+ }
+ expr.value = value
+
+ // textarea and text nodes has no attribute name
+ if (!attrName) {
+ // about #815 w/o replace: the browser converts the value to a string,
+ // the comparison by "==" does too, but not in the server
+ value += ''
+ // test for parent avoids error with invalid assignment to nodeValue
+ if (parent) {
+ // cache the parent node because somehow it will become null on IE
+ // on the next iteration
+ expr.parent = parent
+ if (parent.tagName === 'TEXTAREA') {
+ parent.value = value // #1113
+ if (!IE_VERSION) dom.nodeValue = value // #1625 IE throws here, nodeValue
+ } // will be available on 'updated'
+ else dom.nodeValue = value
+ }
+ return
+ }
+
+ // ~~#1612: look for changes in dom.value when updating the value~~
+ if (attrName === 'value') {
+ if (dom.value !== value) {
+ dom.value = value
+ setAttr(dom, attrName, value)
+ }
+ return
+ } else {
+ // remove original attribute
+ remAttr(dom, attrName)
+ }
+
+ // event handler
+ if (isFunction(value)) {
+ setEventHandler(attrName, value, dom, tag)
+
+ // if- conditional
+ } else if (attrName == 'if') {
+ var stub = expr.stub,
+ add = function() { insertTo(stub.parentNode, stub, dom) },
+ remove = function() { insertTo(dom.parentNode, dom, stub) }
+
+ // add to DOM
+ if (value) {
+ if (stub) {
+ add()
+ dom.inStub = false
+ // avoid to trigger the mount event if the tags is not visible yet
+ // maybe we can optimize this avoiding to mount the tag at all
+ if (!isInStub(dom)) {
+ walk(dom, function(el) {
+ if (el._tag && !el._tag.isMounted)
+ el._tag.isMounted = !!el._tag.trigger('mount')
+ })
+ }
+ }
+ // remove from DOM
+ } else {
+ stub = expr.stub = stub || document.createTextNode('')
+ // if the parentNode is defined we can easily replace the tag
+ if (dom.parentNode)
+ remove()
+ // otherwise we need to wait the updated event
+ else (tag.parent || tag).one('updated', remove)
+
+ dom.inStub = true
+ }
+ // show / hide
+ } else if (attrName === 'show') {
+ dom.style.display = value ? '' : 'none'
+
+ } else if (attrName === 'hide') {
+ dom.style.display = value ? 'none' : ''
+
+ } else if (expr.bool) {
+ dom[attrName] = value
+ if (value) setAttr(dom, attrName, attrName)
+ if (FIREFOX && attrName === 'selected' && dom.tagName === 'OPTION') {
+ dom.__riot1374 = value // #1374
+ }
+
+ } else if (value === 0 || value && typeof value !== T_OBJECT) {
+ //
+ if (startsWith(attrName, RIOT_PREFIX) && attrName != RIOT_TAG) {
+ attrName = attrName.slice(RIOT_PREFIX.length)
+ }
+ setAttr(dom, attrName, value)
+ }
+
+ })
+
+}
+/**
+ * Specialized function for looping an array-like collection with `each={}`
+ * @param { Array } els - collection of items
+ * @param {Function} fn - callback function
+ * @returns { Array } the array looped
+ */
+function each(els, fn) {
+ var len = els ? els.length : 0
+
+ for (var i = 0, el; i < len; i++) {
+ el = els[i]
+ // return false -> current item was removed by fn during the loop
+ if (el != null && fn(el, i) === false) i--
+ }
+ return els
+}
+
+/**
+ * Detect if the argument passed is a function
+ * @param { * } v - whatever you want to pass to this function
+ * @returns { Boolean } -
+ */
+function isFunction(v) {
+ return typeof v === T_FUNCTION || false // avoid IE problems
+}
+
+/**
+ * Get the outer html of any DOM node SVGs included
+ * @param { Object } el - DOM node to parse
+ * @returns { String } el.outerHTML
+ */
+function getOuterHTML(el) {
+ if (el.outerHTML) return el.outerHTML
+ // some browsers do not support outerHTML on the SVGs tags
+ else {
+ var container = mkEl('div')
+ container.appendChild(el.cloneNode(true))
+ return container.innerHTML
+ }
+}
+
+/**
+ * Set the inner html of any DOM node SVGs included
+ * @param { Object } container - DOM node where we will inject the new html
+ * @param { String } html - html to inject
+ */
+function setInnerHTML(container, html) {
+ if (typeof container.innerHTML != T_UNDEF) container.innerHTML = html
+ // some browsers do not support innerHTML on the SVGs tags
+ else {
+ var doc = new DOMParser().parseFromString(html, 'application/xml')
+ container.appendChild(
+ container.ownerDocument.importNode(doc.documentElement, true)
+ )
+ }
+}
+
+/**
+ * Checks wether a DOM node must be considered part of an svg document
+ * @param { String } name - tag name
+ * @returns { Boolean } -
+ */
+function isSVGTag(name) {
+ return ~SVG_TAGS_LIST.indexOf(name)
+}
+
+/**
+ * Detect if the argument passed is an object, exclude null.
+ * NOTE: Use isObject(x) && !isArray(x) to excludes arrays.
+ * @param { * } v - whatever you want to pass to this function
+ * @returns { Boolean } -
+ */
+function isObject(v) {
+ return v && typeof v === T_OBJECT // typeof null is 'object'
+}
+
+/**
+ * Remove any DOM attribute from a node
+ * @param { Object } dom - DOM node we want to update
+ * @param { String } name - name of the property we want to remove
+ */
+function remAttr(dom, name) {
+ dom.removeAttribute(name)
+}
+
+/**
+ * Convert a string containing dashes to camel case
+ * @param { String } string - input string
+ * @returns { String } my-string -> myString
+ */
+function toCamel(string) {
+ return string.replace(/-(\w)/g, function(_, c) {
+ return c.toUpperCase()
+ })
+}
+
+/**
+ * Get the value of any DOM attribute on a node
+ * @param { Object } dom - DOM node we want to parse
+ * @param { String } name - name of the attribute we want to get
+ * @returns { String | undefined } name of the node attribute whether it exists
+ */
+function getAttr(dom, name) {
+ return dom.getAttribute(name)
+}
+
+/**
+ * Set any DOM/SVG attribute
+ * @param { Object } dom - DOM node we want to update
+ * @param { String } name - name of the property we want to set
+ * @param { String } val - value of the property we want to set
+ */
+function setAttr(dom, name, val) {
+ var xlink = XLINK_REGEX.exec(name)
+ if (xlink && xlink[1])
+ dom.setAttributeNS(XLINK_NS, xlink[1], val)
+ else
+ dom.setAttribute(name, val)
+}
+
+/**
+ * Detect the tag implementation by a DOM node
+ * @param { Object } dom - DOM node we need to parse to get its tag implementation
+ * @returns { Object } it returns an object containing the implementation of a custom tag (template and boot function)
+ */
+function getTag(dom) {
+ return dom.tagName && __tagImpl[getAttr(dom, RIOT_TAG_IS) ||
+ getAttr(dom, RIOT_TAG) || dom.tagName.toLowerCase()]
+}
+/**
+ * Add a child tag to its parent into the `tags` object
+ * @param { Object } tag - child tag instance
+ * @param { String } tagName - key where the new tag will be stored
+ * @param { Object } parent - tag instance where the new child tag will be included
+ */
+function addChildTag(tag, tagName, parent) {
+ var cachedTag = parent.tags[tagName]
+
+ // if there are multiple children tags having the same name
+ if (cachedTag) {
+ // if the parent tags property is not yet an array
+ // create it adding the first cached tag
+ if (!isArray(cachedTag))
+ // don't add the same tag twice
+ if (cachedTag !== tag)
+ parent.tags[tagName] = [cachedTag]
+ // add the new nested tag to the array
+ if (!contains(parent.tags[tagName], tag))
+ parent.tags[tagName].push(tag)
+ } else {
+ parent.tags[tagName] = tag
+ }
+}
+
+/**
+ * Move the position of a custom tag in its parent tag
+ * @param { Object } tag - child tag instance
+ * @param { String } tagName - key where the tag was stored
+ * @param { Number } newPos - index where the new tag will be stored
+ */
+function moveChildTag(tag, tagName, newPos) {
+ var parent = tag.parent,
+ tags
+ // no parent no move
+ if (!parent) return
+
+ tags = parent.tags[tagName]
+
+ if (isArray(tags))
+ tags.splice(newPos, 0, tags.splice(tags.indexOf(tag), 1)[0])
+ else addChildTag(tag, tagName, parent)
+}
+
+/**
+ * Create a new child tag including it correctly into its parent
+ * @param { Object } child - child tag implementation
+ * @param { Object } opts - tag options containing the DOM node where the tag will be mounted
+ * @param { String } innerHTML - inner html of the child node
+ * @param { Object } parent - instance of the parent tag including the child custom tag
+ * @returns { Object } instance of the new child tag just created
+ */
+function initChildTag(child, opts, innerHTML, parent) {
+ var tag = new Tag(child, opts, innerHTML),
+ tagName = getTagName(opts.root),
+ ptag = getImmediateCustomParentTag(parent)
+ // fix for the parent attribute in the looped elements
+ tag.parent = ptag
+ // store the real parent tag
+ // in some cases this could be different from the custom parent tag
+ // for example in nested loops
+ tag._parent = parent
+
+ // add this tag to the custom parent tag
+ addChildTag(tag, tagName, ptag)
+ // and also to the real parent tag
+ if (ptag !== parent)
+ addChildTag(tag, tagName, parent)
+ // empty the child node once we got its template
+ // to avoid that its children get compiled multiple times
+ opts.root.innerHTML = ''
+
+ return tag
+}
+
+/**
+ * Loop backward all the parents tree to detect the first custom parent tag
+ * @param { Object } tag - a Tag instance
+ * @returns { Object } the instance of the first custom parent tag found
+ */
+function getImmediateCustomParentTag(tag) {
+ var ptag = tag
+ while (!getTag(ptag.root)) {
+ if (!ptag.parent) break
+ ptag = ptag.parent
+ }
+ return ptag
+}
+
+/**
+ * Helper function to set an immutable property
+ * @param { Object } el - object where the new property will be set
+ * @param { String } key - object key where the new property will be stored
+ * @param { * } value - value of the new property
+* @param { Object } options - set the propery overriding the default options
+ * @returns { Object } - the initial object
+ */
+function defineProperty(el, key, value, options) {
+ Object.defineProperty(el, key, extend({
+ value: value,
+ enumerable: false,
+ writable: false,
+ configurable: true
+ }, options))
+ return el
+}
+
+/**
+ * Get the tag name of any DOM node
+ * @param { Object } dom - DOM node we want to parse
+ * @returns { String } name to identify this dom node in riot
+ */
+function getTagName(dom) {
+ var child = getTag(dom),
+ namedTag = getAttr(dom, 'name'),
+ tagName = namedTag && !tmpl.hasExpr(namedTag) ?
+ namedTag :
+ child ? child.name : dom.tagName.toLowerCase()
+
+ return tagName
+}
+
+/**
+ * Extend any object with other properties
+ * @param { Object } src - source object
+ * @returns { Object } the resulting extended object
+ *
+ * var obj = { foo: 'baz' }
+ * extend(obj, {bar: 'bar', foo: 'bar'})
+ * console.log(obj) => {bar: 'bar', foo: 'bar'}
+ *
+ */
+function extend(src) {
+ var obj, args = arguments
+ for (var i = 1; i < args.length; ++i) {
+ if (obj = args[i]) {
+ for (var key in obj) {
+ // check if this property of the source object could be overridden
+ if (isWritable(src, key))
+ src[key] = obj[key]
+ }
+ }
+ }
+ return src
+}
+
+/**
+ * Check whether an array contains an item
+ * @param { Array } arr - target array
+ * @param { * } item - item to test
+ * @returns { Boolean } Does 'arr' contain 'item'?
+ */
+function contains(arr, item) {
+ return ~arr.indexOf(item)
+}
+
+/**
+ * Check whether an object is a kind of array
+ * @param { * } a - anything
+ * @returns {Boolean} is 'a' an array?
+ */
+function isArray(a) { return Array.isArray(a) || a instanceof Array }
+
+/**
+ * Detect whether a property of an object could be overridden
+ * @param { Object } obj - source object
+ * @param { String } key - object property
+ * @returns { Boolean } is this property writable?
+ */
+function isWritable(obj, key) {
+ var props = Object.getOwnPropertyDescriptor(obj, key)
+ return typeof obj[key] === T_UNDEF || props && props.writable
+}
+
+
+/**
+ * With this function we avoid that the internal Tag methods get overridden
+ * @param { Object } data - options we want to use to extend the tag instance
+ * @returns { Object } clean object without containing the riot internal reserved words
+ */
+function cleanUpData(data) {
+ if (!(data instanceof Tag) && !(data && typeof data.trigger == T_FUNCTION))
+ return data
+
+ var o = {}
+ for (var key in data) {
+ if (!RESERVED_WORDS_BLACKLIST.test(key)) o[key] = data[key]
+ }
+ return o
+}
+
+/**
+ * Walk down recursively all the children tags starting dom node
+ * @param { Object } dom - starting node where we will start the recursion
+ * @param { Function } fn - callback to transform the child node just found
+ */
+function walk(dom, fn) {
+ if (dom) {
+ // stop the recursion
+ if (fn(dom) === false) return
+ else {
+ dom = dom.firstChild
+
+ while (dom) {
+ walk(dom, fn)
+ dom = dom.nextSibling
+ }
+ }
+ }
+}
+
+/**
+ * Minimize risk: only zero or one _space_ between attr & value
+ * @param { String } html - html string we want to parse
+ * @param { Function } fn - callback function to apply on any attribute found
+ */
+function walkAttributes(html, fn) {
+ var m,
+ re = /([-\w]+) ?= ?(?:"([^"]*)|'([^']*)|({[^}]*}))/g
+
+ while (m = re.exec(html)) {
+ fn(m[1].toLowerCase(), m[2] || m[3] || m[4])
+ }
+}
+
+/**
+ * Check whether a DOM node is in stub mode, useful for the riot 'if' directive
+ * @param { Object } dom - DOM node we want to parse
+ * @returns { Boolean } -
+ */
+function isInStub(dom) {
+ while (dom) {
+ if (dom.inStub) return true
+ dom = dom.parentNode
+ }
+ return false
+}
+
+/**
+ * Create a generic DOM node
+ * @param { String } name - name of the DOM node we want to create
+ * @param { Boolean } isSvg - should we use a SVG as parent node?
+ * @returns { Object } DOM node just created
+ */
+function mkEl(name, isSvg) {
+ return isSvg ?
+ document.createElementNS('http://www.w3.org/2000/svg', 'svg') :
+ document.createElement(name)
+}
+
+/**
+ * Shorter and fast way to select multiple nodes in the DOM
+ * @param { String } selector - DOM selector
+ * @param { Object } ctx - DOM node where the targets of our search will is located
+ * @returns { Object } dom nodes found
+ */
+function $$(selector, ctx) {
+ return (ctx || document).querySelectorAll(selector)
+}
+
+/**
+ * Shorter and fast way to select a single node in the DOM
+ * @param { String } selector - unique dom selector
+ * @param { Object } ctx - DOM node where the target of our search will is located
+ * @returns { Object } dom node found
+ */
+function $(selector, ctx) {
+ return (ctx || document).querySelector(selector)
+}
+
+/**
+ * Simple object prototypal inheritance
+ * @param { Object } parent - parent object
+ * @returns { Object } child instance
+ */
+function inherit(parent) {
+ function Child() {}
+ Child.prototype = parent
+ return new Child()
+}
+
+/**
+ * Get the name property needed to identify a DOM node in riot
+ * @param { Object } dom - DOM node we need to parse
+ * @returns { String | undefined } give us back a string to identify this dom node
+ */
+function getNamedKey(dom) {
+ return getAttr(dom, 'id') || getAttr(dom, 'name')
+}
+
+/**
+ * Set the named properties of a tag element
+ * @param { Object } dom - DOM node we need to parse
+ * @param { Object } parent - tag instance where the named dom element will be eventually added
+ * @param { Array } keys - list of all the tag instance properties
+ */
+function setNamed(dom, parent, keys) {
+ // get the key value we want to add to the tag instance
+ var key = getNamedKey(dom),
+ isArr,
+ // add the node detected to a tag instance using the named property
+ add = function(value) {
+ // avoid to override the tag properties already set
+ if (contains(keys, key)) return
+ // check whether this value is an array
+ isArr = isArray(value)
+ // if the key was never set
+ if (!value)
+ // set it once on the tag instance
+ parent[key] = dom
+ // if it was an array and not yet set
+ else if (!isArr || isArr && !contains(value, dom)) {
+ // add the dom node into the array
+ if (isArr)
+ value.push(dom)
+ else
+ parent[key] = [value, dom]
+ }
+ }
+
+ // skip the elements with no named properties
+ if (!key) return
+
+ // check whether this key has been already evaluated
+ if (tmpl.hasExpr(key))
+ // wait the first updated event only once
+ parent.one('mount', function() {
+ key = getNamedKey(dom)
+ add(parent[key])
+ })
+ else
+ add(parent[key])
+
+}
+
+/**
+ * Faster String startsWith alternative
+ * @param { String } src - source string
+ * @param { String } str - test string
+ * @returns { Boolean } -
+ */
+function startsWith(src, str) {
+ return src.slice(0, str.length) === str
+}
+
+/**
+ * requestAnimationFrame function
+ * Adapted from https://gist.github.com/paulirish/1579671, license MIT
+ */
+var rAF = (function (w) {
+ var raf = w.requestAnimationFrame ||
+ w.mozRequestAnimationFrame || w.webkitRequestAnimationFrame
+
+ if (!raf || /iP(ad|hone|od).*OS 6/.test(w.navigator.userAgent)) { // buggy iOS6
+ var lastTime = 0
+
+ raf = function (cb) {
+ var nowtime = Date.now(), timeout = Math.max(16 - (nowtime - lastTime), 0)
+ setTimeout(function () { cb(lastTime = nowtime + timeout) }, timeout)
+ }
+ }
+ return raf
+
+})(window || {})
+
+/**
+ * Mount a tag creating new Tag instance
+ * @param { Object } root - dom node where the tag will be mounted
+ * @param { String } tagName - name of the riot tag we want to mount
+ * @param { Object } opts - options to pass to the Tag instance
+ * @returns { Tag } a new Tag instance
+ */
+function mountTo(root, tagName, opts) {
+ var tag = __tagImpl[tagName],
+ // cache the inner HTML to fix #855
+ innerHTML = root._innerHTML = root._innerHTML || root.innerHTML
+
+ // clear the inner html
+ root.innerHTML = ''
+
+ if (tag && root) tag = new Tag(tag, { root: root, opts: opts }, innerHTML)
+
+ if (tag && tag.mount) {
+ tag.mount()
+ // add this tag to the virtualDom variable
+ if (!contains(__virtualDom, tag)) __virtualDom.push(tag)
+ }
+
+ return tag
+}
+/**
+ * Riot public api
+ */
+
+// share methods for other riot parts, e.g. compiler
+riot.util = { brackets: brackets, tmpl: tmpl }
+
+/**
+ * Create a mixin that could be globally shared across all the tags
+ */
+riot.mixin = (function() {
+ var mixins = {},
+ globals = mixins[GLOBAL_MIXIN] = {},
+ _id = 0
+
+ /**
+ * Create/Return a mixin by its name
+ * @param { String } name - mixin name (global mixin if object)
+ * @param { Object } mixin - mixin logic
+ * @param { Boolean } g - is global?
+ * @returns { Object } the mixin logic
+ */
+ return function(name, mixin, g) {
+ // Unnamed global
+ if (isObject(name)) {
+ riot.mixin('__unnamed_'+_id++, name, true)
+ return
+ }
+
+ var store = g ? globals : mixins
+
+ // Getter
+ if (!mixin) {
+ if (typeof store[name] === T_UNDEF) {
+ throw new Error('Unregistered mixin: ' + name)
+ }
+ return store[name]
+ }
+ // Setter
+ if (isFunction(mixin)) {
+ extend(mixin.prototype, store[name] || {})
+ store[name] = mixin
+ }
+ else {
+ store[name] = extend(store[name] || {}, mixin)
+ }
+ }
+
+})()
+
+/**
+ * Create a new riot tag implementation
+ * @param { String } name - name/id of the new riot tag
+ * @param { String } html - tag template
+ * @param { String } css - custom tag css
+ * @param { String } attrs - root tag attributes
+ * @param { Function } fn - user function
+ * @returns { String } name/id of the tag just created
+ */
+riot.tag = function(name, html, css, attrs, fn) {
+ if (isFunction(attrs)) {
+ fn = attrs
+ if (/^[\w\-]+\s?=/.test(css)) {
+ attrs = css
+ css = ''
+ } else attrs = ''
+ }
+ if (css) {
+ if (isFunction(css)) fn = css
+ else styleManager.add(css)
+ }
+ name = name.toLowerCase()
+ __tagImpl[name] = { name: name, tmpl: html, attrs: attrs, fn: fn }
+ return name
+}
+
+/**
+ * Create a new riot tag implementation (for use by the compiler)
+ * @param { String } name - name/id of the new riot tag
+ * @param { String } html - tag template
+ * @param { String } css - custom tag css
+ * @param { String } attrs - root tag attributes
+ * @param { Function } fn - user function
+ * @returns { String } name/id of the tag just created
+ */
+riot.tag2 = function(name, html, css, attrs, fn) {
+ if (css) styleManager.add(css)
+ //if (bpair) riot.settings.brackets = bpair
+ __tagImpl[name] = { name: name, tmpl: html, attrs: attrs, fn: fn }
+ return name
+}
+
+/**
+ * Mount a tag using a specific tag implementation
+ * @param { String } selector - tag DOM selector
+ * @param { String } tagName - tag implementation name
+ * @param { Object } opts - tag logic
+ * @returns { Array } new tags instances
+ */
+riot.mount = function(selector, tagName, opts) {
+
+ var els,
+ allTags,
+ tags = []
+
+ // helper functions
+
+ function addRiotTags(arr) {
+ var list = ''
+ each(arr, function (e) {
+ if (!/[^-\w]/.test(e)) {
+ e = e.trim().toLowerCase()
+ list += ',[' + RIOT_TAG_IS + '="' + e + '"],[' + RIOT_TAG + '="' + e + '"]'
+ }
+ })
+ return list
+ }
+
+ function selectAllTags() {
+ var keys = Object.keys(__tagImpl)
+ return keys + addRiotTags(keys)
+ }
+
+ function pushTags(root) {
+ if (root.tagName) {
+ var riotTag = getAttr(root, RIOT_TAG_IS) || getAttr(root, RIOT_TAG)
+
+ // have tagName? force riot-tag to be the same
+ if (tagName && riotTag !== tagName) {
+ riotTag = tagName
+ setAttr(root, RIOT_TAG_IS, tagName)
+ setAttr(root, RIOT_TAG, tagName) // this will be removed in riot 3.0.0
+ }
+ var tag = mountTo(root, riotTag || root.tagName.toLowerCase(), opts)
+
+ if (tag) tags.push(tag)
+ } else if (root.length) {
+ each(root, pushTags) // assume nodeList
+ }
+ }
+
+ // ----- mount code -----
+
+ // inject styles into DOM
+ styleManager.inject()
+
+ if (isObject(tagName)) {
+ opts = tagName
+ tagName = 0
+ }
+
+ // crawl the DOM to find the tag
+ if (typeof selector === T_STRING) {
+ if (selector === '*')
+ // select all the tags registered
+ // and also the tags found with the riot-tag attribute set
+ selector = allTags = selectAllTags()
+ else
+ // or just the ones named like the selector
+ selector += addRiotTags(selector.split(/, */))
+
+ // make sure to pass always a selector
+ // to the querySelectorAll function
+ els = selector ? $$(selector) : []
+ }
+ else
+ // probably you have passed already a tag or a NodeList
+ els = selector
+
+ // select all the registered and mount them inside their root elements
+ if (tagName === '*') {
+ // get all custom tags
+ tagName = allTags || selectAllTags()
+ // if the root els it's just a single tag
+ if (els.tagName)
+ els = $$(tagName, els)
+ else {
+ // select all the children for all the different root elements
+ var nodeList = []
+ each(els, function (_el) {
+ nodeList.push($$(tagName, _el))
+ })
+ els = nodeList
+ }
+ // get rid of the tagName
+ tagName = 0
+ }
+
+ pushTags(els)
+
+ return tags
+}
+
+/**
+ * Update all the tags instances created
+ * @returns { Array } all the tags instances
+ */
+riot.update = function() {
+ return each(__virtualDom, function(tag) {
+ tag.update()
+ })
+}
+
+/**
+ * Export the Virtual DOM
+ */
+riot.vdom = __virtualDom
+
+/**
+ * Export the Tag constructor
+ */
+riot.Tag = Tag
+ // support CommonJS, AMD & browser
+ /* istanbul ignore next */
+ if (typeof exports === T_OBJECT)
+ module.exports = riot
+ else if (typeof define === T_FUNCTION && typeof define.amd !== T_UNDEF)
+ define(function() { return riot })
+ else
+ window.riot = riot
+
+})(typeof window != 'undefined' ? window : void 0);
ADDED public/riot-3.13.2/test/requirejs.html
Index: public/riot-3.13.2/test/requirejs.html
==================================================================
--- public/riot-3.13.2/test/requirejs.html
+++ public/riot-3.13.2/test/requirejs.html
@@ -0,0 +1,29 @@
+
+
+
+ Riot requirejs tests
+
+
+
+
+
+
+
+
+
+
+
+
ADDED public/riot-3.13.2/test/saucelabs-browsers.js
Index: public/riot-3.13.2/test/saucelabs-browsers.js
==================================================================
--- public/riot-3.13.2/test/saucelabs-browsers.js
+++ public/riot-3.13.2/test/saucelabs-browsers.js
@@ -0,0 +1,83 @@
+// we will re-enable the broken browsers once saucelabs will fix all the timeout issues
+module.exports = {
+ browsers: {
+ slIpad: {
+ base: 'SauceLabs',
+ browserName: 'ipad',
+ version: '10.3'
+ },
+ slIphone6: {
+ base: 'SauceLabs',
+ browserName: 'iphone',
+ version: '10.3'
+ },
+ slSafari8: {
+ base: 'SauceLabs',
+ browserName: 'safari',
+ platform: 'OS X 10.10'
+ },
+ slSafari10: {
+ base: 'SauceLabs',
+ browserName: 'safari',
+ platform: 'OS X 10.11'
+ },
+ slSafari11: {
+ base: 'SauceLabs',
+ browserName: 'safari',
+ platform: 'OS X 10.13'
+ },
+ slIE9: {
+ base: 'SauceLabs',
+ browserName: 'internet explorer',
+ platform: 'Windows 7',
+ version: '9'
+ },
+ slIE10: {
+ base: 'SauceLabs',
+ browserName: 'internet explorer',
+ platform: 'Windows 7',
+ version: '10'
+ },
+ slIE11: {
+ base: 'SauceLabs',
+ browserName: 'internet explorer',
+ platform: 'Windows 7',
+ version: '11'
+ },
+ slEdge14: {
+ base: 'SauceLabs',
+ browserName: 'MicrosoftEdge',
+ version: '14',
+ platform: 'Windows 10'
+ },
+ slEdge15: {
+ base: 'SauceLabs',
+ browserName: 'MicrosoftEdge',
+ version: '15',
+ platform: 'Windows 10'
+ },
+ slEdge: {
+ base: 'SauceLabs',
+ browserName: 'MicrosoftEdge',
+ platform: 'Windows 10'
+ },
+ slChrome: {
+ base: 'SauceLabs',
+ browserName: 'chrome'
+ },
+ slFirefox: {
+ base: 'SauceLabs',
+ browserName: 'firefox'
+ },
+ slAndroid5: {
+ base: 'SauceLabs',
+ browserName: 'android',
+ version: '5.1'
+ },
+ slAndroid4: {
+ base: 'SauceLabs',
+ browserName: 'android',
+ version: '4.4'
+ }
+ }
+}
ADDED public/riot-3.13.2/test/specs/browser/compiler/compiler.spec.js
Index: public/riot-3.13.2/test/specs/browser/compiler/compiler.spec.js
==================================================================
--- public/riot-3.13.2/test/specs/browser/compiler/compiler.spec.js
+++ public/riot-3.13.2/test/specs/browser/compiler/compiler.spec.js
@@ -0,0 +1,168 @@
+/* global defaultBrackets */
+
+import {
+ injectHTML,
+ $,
+ getRiotStyles
+} from '../../../helpers/index'
+
+//import '../../../tag/bug-2369.tag'
+
+
+describe('Riot compiler', function() {
+
+ beforeEach(function() {
+ // adding some custom riot parsers
+ // css
+ riot.parsers.css.myparser = function(tag, css) {
+ return css.replace(/@tag/, tag).replace(' 3px ', ' 4px ')
+ }
+ // js
+ riot.parsers.js.myparser = function(js) {
+ return js.replace(/@version/, '1.0.0')
+ }
+ })
+
+ it('compiler performance', function() {
+ var src = [
+ '',
+ ' { opts.baz } { bar }
',
+
+ ' this.bar = "romutus"',
+
+ ' ',
+ '',
+ ' ',
+ ' ',
+ ' { kama }
',
+
+ ' this.times = [ 1, 3, 5 ]',
+ ' this.kama = "jooo"',
+ ' '
+ ].join('\n')
+
+ // check compile is actually compiling the source
+ expect(riot.compile(src, true)).to.contain("('timetable', '")
+
+ // compile timer 1000 times and see how long it takes
+ var begin = Date.now()
+
+ for (var i = 0; i < 1000; i++) {
+ riot.compile(src, true)
+ }
+
+ expect(Date.now() - begin).to.be.below(1500) // compiler does more now
+
+ })
+
+ it('compile a custom tag using custom css and js parsers', function(done) {
+
+ injectHTML(' ')
+
+ riot.compile('./tag/~custom-parsers.tag', function() {
+
+ var tag = riot.mount('custom-parsers')[0],
+ styles = getRiotStyles(riot)
+
+ expect(tag).to.be.an('object')
+ expect(tag.version).to.be.equal('1.0.0')
+ expect(styles).to.match(/\bcustom-parsers ?\{\s*color: red;}/)
+
+ tag.unmount()
+ done()
+ })
+
+ })
+
+ // this test in theory goes in style.spec.js
+ it('scoped css tag supports htm5 syntax, multiple style tags', function (done) {
+ injectHTML(' ')
+ this.timeout(5000)
+ riot.compile(['./tag/~style-tag3.tag'], function() {
+ checkCSS(riot.mount('style-tag3')[0], '4px')
+ delete riot.parsers.css.cssup
+ function checkCSS(t, x) {
+ t.update()
+ var e = t.root.firstElementChild
+ expect(e.tagName).to.be.equal('P')
+ expect(window.getComputedStyle(e, null).borderTopWidth).to.be.equal(x)
+ t.unmount()
+ }
+ done()
+ })
+ })
+
+ it('Passing options to the compiler through compile (v2.3.12)', function () {
+ var str = '\n \n
\nclick(e){}\n ',
+ result = riot.compile(str, true, {compact: true, type: 'none'})
+ expect(result).to.contain('
') // compact: true
+ expect(result).to.contain('\nclick(e){}\n') // type: none
+ })
+
+ it('compile detect changes in riot.settings.brackets', function() {
+ var compiled
+
+ // change the brackets
+ riot.util.brackets.set('{{ }}')
+ expect(riot.settings.brackets).to.be.equal('{{ }}')
+ compiled = riot.compile('{{ time }} and { time } ', true)
+ expect(compiled).to.contain("riot.tag2('my', '{{time}} and { time }',")
+
+ // restore using riot.settings
+ riot.settings.brackets = defaultBrackets
+ compiled = riot.compile('{ time } and { time } ', true)
+ expect(riot.util.brackets.settings.brackets).to.be.equal(defaultBrackets)
+ expect(compiled).to.contain("riot.tag2('my', '{time} and {time}',")
+
+ // change again, now with riot.settings
+ riot.settings.brackets = '{{ }}'
+ compiled = riot.compile('{{ time }} and { time } ', true)
+ expect(riot.util.brackets.settings.brackets).to.be.equal('{{ }}')
+ expect(compiled).to.contain("riot.tag2('my', '{{time}} and { time }',")
+
+ riot.util.brackets.set(undefined)
+ expect(riot.settings.brackets).to.be.equal(defaultBrackets)
+ compiled = riot.compile('{ time } and { time } ', true)
+ expect(compiled).to.contain("riot.tag2('my', '{time} and {time}',")
+ })
+
+ it('mount search data-is attributes for tag names only #1463', function () {
+ var
+ names = ['x-my_tag1', 'x-my-tag2', 'x-my-3tag', 'x-m1-3tag'],
+ templ = '<@>X@>',
+ name
+
+ // test browser capability for match unquoted chars in [-_A-Z]
+ for (let i = 0; i < names.length; ++i) {
+ injectHTML(`
`)
+ riot.compile(templ.replace(/@/g, names[i]))
+ let tag = riot.mount(names[i])[0]
+ expect($('*[data-is=' + names[i] + ']').innerHTML).to.be.equal('X')
+ tag.unmount()
+ }
+
+ // double quotes work, we can't mount html element named "22"
+ name = 'x-my-tag3'
+ injectHTML(`<${name} name="22">${name}>`)
+ riot.compile(templ.replace(/@/g, name))
+ var tag = riot.mount('*[name="22"]')[0]
+ expect($(name).innerHTML).to.be.equal('X')
+ tag.unmount()
+ })
+
+ it('tags containing regex get properly compiled', function(done) {
+ injectHTML(' ')
+ riot.compile('./tag/bug-2369.tag', function () {
+ const tag = riot.mount('bug-2369')[0]
+ expect(tag.root).to.be.ok
+ tag.unmount()
+ done()
+ })
+ })
+
+ it('throw compile.error in case a file will be not found', function(done) {
+ // override the compile error function
+ riot.compile.error = () => done()
+ riot.compile('./foo/bar.tag')
+ })
+})
ADDED public/riot-3.13.2/test/specs/browser/index.js
Index: public/riot-3.13.2/test/specs/browser/index.js
==================================================================
--- public/riot-3.13.2/test/specs/browser/index.js
+++ public/riot-3.13.2/test/specs/browser/index.js
@@ -0,0 +1,13 @@
+// make expect globally available
+window.defaultBrackets = riot.settings.brackets
+window.expect = chai.expect // eslint-disable-line
+
+before(function() {
+ riot.unregister('riot-tmp')
+ riot.unregister('riot-tmp-value')
+ riot.unregister('riot-tmp-sub')
+})
+
+after(function() {
+ riot.settings.brackets = window.defaultBrackets
+})
ADDED public/riot-3.13.2/test/specs/browser/riot/core.spec.js
Index: public/riot-3.13.2/test/specs/browser/riot/core.spec.js
==================================================================
--- public/riot-3.13.2/test/specs/browser/riot/core.spec.js
+++ public/riot-3.13.2/test/specs/browser/riot/core.spec.js
@@ -0,0 +1,1703 @@
+import {
+ injectHTML,
+ $,
+ $$,
+ IE_VERSION,
+ normalizeHTML,
+ fireEvent,
+ getCarrotPos,
+ setCarrotPos
+} from '../../../helpers/index'
+
+// include special tags to test specific features
+import '../../../tag/v-dom-1.tag'
+import '../../../tag/v-dom-2.tag'
+import '../../../tag/timetable.tag'
+import '../../../tag/nested-child.tag'
+import '../../../tag/top-attributes.tag'
+import '../../../tag/preserve-attr.tag'
+import '../../../tag/svg-attr.tag'
+import '../../../tag/named-child.tag'
+import '../../../tag/deferred-mount.tag'
+import '../../../tag/prevent-update.tag'
+import '../../../tag/expression-eval-count.tag'
+import '../../../tag/multi-named.tag'
+import '../../../tag/named-data-ref.tag'
+import '../../../tag/input-number.tag'
+import '../../../tag/input-values.tag'
+import '../../../tag/input-updated.tag'
+import '../../../tag/nested-riot.tag'
+import '../../../tag/treeview.tag'
+import '../../../tag/events.tag'
+import '../../../tag/runtime-event-listener-switch.tag'
+import '../../../tag/should-update.tag'
+import '../../../tag/should-update-opts.tag'
+import '../../../tag/observable-attr.tag'
+import '../../../tag/virtual-nested-unmount.tag'
+import '../../../tag/virtual-conditional.tag'
+import '../../../tag/form-controls.tag'
+import '../../../tag/data-is.tag'
+import '../../../tag/virtual-nested-component.tag'
+import '../../../tag/dynamic-data-is.tag'
+import '../../../tag/update-context.tag'
+import '../../../tag/dynamic-virtual.tag'
+import '../../../tag/multiple-select.tag'
+import '../../../tag/dynamic-nested.tag'
+import '../../../tag/bug-2629.tag'
+
+describe('Riot core', function() {
+ it('Riot exists', function () {
+ expect(riot).to.be.not.undefined
+ })
+
+ before(function() {
+ // general tag
+ riot.tag('test', 'val: { opts.val }
')
+ })
+
+ it('it should export the current riot build version as string', function() {
+ expect(riot.version).to.be.a('string')
+ })
+
+ it('populates the vdom property correctly on riot global', function() {
+ injectHTML(' ')
+ injectHTML(' ')
+ var tags = riot.mount('v-dom-1, v-dom-2')
+
+ expect(tags.length).to.be.equal(2)
+ expect(riot.util.vdom).to.have.length(tags.length)
+ riot.util.vdom.forEach(function(tag, i) {
+ expect(tag).to.be.equal(tags[i])
+ })
+ tags.forEach(tag => tag.unmount())
+ })
+
+ it('riot can be extended', function() {
+ riot.route = function() {}
+
+ expect(riot.route).to.be.a('function')
+
+ riot.util.tmpl.errorHandle = function() {}
+
+ expect(riot.util.tmpl.errorHandle).to.be.a('function')
+ })
+
+ it('mount and unmount', function() {
+
+ injectHTML([
+ ' ',
+ '
',
+ '
'
+ ])
+
+ var tag = riot.mount('test', { val: 10 })[0],
+ tag2 = riot.mount('#foo', 'test', { val: 30 })[0],
+ tag3 = riot.mount($('#bar'), 'test', { val: 50 })[0]
+
+ expect(normalizeHTML(tag.root.innerHTML)).to.be.equal('val: 10
')
+ expect(normalizeHTML(tag2.root.innerHTML)).to.be.equal('val: 30
')
+ expect(normalizeHTML(tag3.root.innerHTML)).to.be.equal('val: 50
')
+
+ tag.unmount()
+ tag2.unmount()
+ tag3.unmount(true)
+
+ expect(tag3.isMounted).to.be.equal(false)
+
+ expect($$('test').length).to.be.equal(0)
+ expect($('#foo')).to.be.equal(null)
+ expect($('#bar')).to.not.be.equal(null)
+
+ expect(tag.root._tag).to.be.equal(undefined)
+ expect(tag2.root._tag).to.be.equal(undefined)
+ expect(tag3.root._tag).to.be.equal(undefined)
+
+ tag3.root.parentNode.removeChild(tag3.root)
+
+ })
+
+ it('node should not preserve attributes from tag mounted on it when it is unmounted', function() {
+ injectHTML('
')
+
+ var tag = riot.mount('#node', 'top-attributes', { cls: 'test' })[0]
+
+ expect(tag.root.hasAttribute('class')).to.be.equal(true)
+ expect(tag.root.hasAttribute('style')).to.be.equal(true)
+ expect(tag.root.hasAttribute('data-nqlast')).to.be.equal(true)
+
+ tag.unmount()
+
+ expect(tag.root.hasAttribute('class')).to.be.equal(false)
+ expect(tag.root.hasAttribute('style')).to.be.equal(false)
+ expect(tag.root.hasAttribute('data-nqlast')).to.be.equal(false)
+ })
+
+ it('mount a tag mutiple times', function() {
+
+ injectHTML([
+ // mount the same tag multiple times
+ '
'
+
+ ])
+
+ var tag = riot.mount('#multi-mount-container-1', 'test', { val: 300 })[0]
+
+ expect(normalizeHTML(tag.root.innerHTML)).to.be.equal('val: 300
')
+
+ riot.tag('test-h', '{ x }
', function() { this.x = 'ok'})
+
+ tag = riot.mount('#multi-mount-container-1', 'test-h')[0]
+
+ expect(normalizeHTML(tag.root.innerHTML)).to.be.equal('ok
')
+
+ tag.unmount()
+
+ })
+
+ it('compiles and unmount the children tags', function(done) {
+
+ injectHTML(' ')
+
+ this.timeout(5000)
+
+ var ticks = 0,
+ tag = riot.mount('timetable', {
+ start: 0,
+ ontick: function() {
+ ticks++
+ }
+ })[0]
+
+ expect($$('timer', tag.root).length).to.be.equal(3)
+
+ riot.update()
+
+ expect(tag.tags.foo).to.not.be.equal(undefined)
+
+ tag.unmount()
+
+ // no time neither for one tick
+ // because the tag got unMounted too early
+ setTimeout(function() {
+ expect(ticks).to.be.equal(0)
+ done()
+ }, 1200)
+
+ })
+
+ it('mount a tag mutiple times using "*"', function() {
+
+ injectHTML([
+ // multple mount using *
+ '',
+ ' ',
+ ' ',
+ ' ',
+ '
'
+ ])
+
+ riot.tag('test-i', '{ x }
', function() { this.x = 'ok'})
+ riot.tag('test-l', '{ x }
', function() { this.x = 'ok'})
+ riot.tag('test-m', '{ x }
', function() { this.x = 'ok'})
+
+ const container = $('#multi-mount-container-2')
+ var subTags = riot.mount('#multi-mount-container-2', '*')
+
+ expect(subTags.length).to.be.equal(3)
+
+ subTags = riot.mount(container, '*')
+
+ expect(subTags.length).to.be.equal(3)
+
+ subTags.forEach(tag => tag.unmount())
+ container.parentNode.removeChild(container)
+ })
+
+ it('avoid to mount unregistered tags', function() {
+ injectHTML(' ')
+
+ riot.tag('riot-tmp', 'hello
')
+ riot.unregister('riot-tmp')
+
+ const tags = riot.mount('*')
+
+ expect(tags).to.be.have.length(0)
+
+ const node = $('riot-tmp')
+
+ node.parentNode.removeChild(node)
+ })
+
+ it('remove style of unregistered tags out of document', function() {
+ injectHTML(' ')
+
+ try {
+ riot.tag('riot-tmp-with-style', 'hello
', 'riot-tmp-with-style { font-size: 1rem; }')
+
+ riot.mount('*') // ensure
+
ADDED public/riot-3.13.2/test/tag/script-tag.tag
Index: public/riot-3.13.2/test/tag/script-tag.tag
==================================================================
--- public/riot-3.13.2/test/tag/script-tag.tag
+++ public/riot-3.13.2/test/tag/script-tag.tag
@@ -0,0 +1,7 @@
+
+ { foo }
+
+
+
ADDED public/riot-3.13.2/test/tag/select-test.tag
Index: public/riot-3.13.2/test/tag/select-test.tag
==================================================================
--- public/riot-3.13.2/test/tag/select-test.tag
+++ public/riot-3.13.2/test/tag/select-test.tag
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+ (choose)
+
+
+
+ { opt }
+
+
+
+ (choose)
+ { opt }
+
+
+
+ { opt }
+ { opt }
+
+
+
+
+ { opt }
+
+
+
+
+
+ { opt }
+
+ (choose)
+
+
+
+
+ { opt }
+
+ (choose)
+
+ { opt }
+
+
+
+
+
+ { opt }
+
+
+
ADDED public/riot-3.13.2/test/tag/shared-opts.tag
Index: public/riot-3.13.2/test/tag/shared-opts.tag
==================================================================
--- public/riot-3.13.2/test/tag/shared-opts.tag
+++ public/riot-3.13.2/test/tag/shared-opts.tag
@@ -0,0 +1,4 @@
+
+
+ { opts.val }
+
ADDED public/riot-3.13.2/test/tag/should-update-opts.tag
Index: public/riot-3.13.2/test/tag/should-update-opts.tag
==================================================================
--- public/riot-3.13.2/test/tag/should-update-opts.tag
+++ public/riot-3.13.2/test/tag/should-update-opts.tag
@@ -0,0 +1,13 @@
+
+ { count }
+
+ this.count = 0
+
+ shouldUpdate(data, opts) {
+ return opts.shouldUpdate
+ }
+
+ this.on('update', function() {
+ this.count ++
+ })
+
ADDED public/riot-3.13.2/test/tag/should-update.tag
Index: public/riot-3.13.2/test/tag/should-update.tag
==================================================================
--- public/riot-3.13.2/test/tag/should-update.tag
+++ public/riot-3.13.2/test/tag/should-update.tag
@@ -0,0 +1,11 @@
+
+ { count }
+
+ this.count = 0
+
+ shouldUpdate(data) { return data }
+
+ this.on('update', function() {
+ this.count ++
+ })
+
ADDED public/riot-3.13.2/test/tag/style-tag.tag
Index: public/riot-3.13.2/test/tag/style-tag.tag
==================================================================
--- public/riot-3.13.2/test/tag/style-tag.tag
+++ public/riot-3.13.2/test/tag/style-tag.tag
@@ -0,0 +1,5 @@
+
+
+
ADDED public/riot-3.13.2/test/tag/style-tag2.tag
Index: public/riot-3.13.2/test/tag/style-tag2.tag
==================================================================
--- public/riot-3.13.2/test/tag/style-tag2.tag
+++ public/riot-3.13.2/test/tag/style-tag2.tag
@@ -0,0 +1,5 @@
+
+
+
ADDED public/riot-3.13.2/test/tag/style-tag4.tag
Index: public/riot-3.13.2/test/tag/style-tag4.tag
==================================================================
--- public/riot-3.13.2/test/tag/style-tag4.tag
+++ public/riot-3.13.2/test/tag/style-tag4.tag
@@ -0,0 +1,6 @@
+
+
+
+
ADDED public/riot-3.13.2/test/tag/svg-attr.tag
Index: public/riot-3.13.2/test/tag/svg-attr.tag
==================================================================
--- public/riot-3.13.2/test/tag/svg-attr.tag
+++ public/riot-3.13.2/test/tag/svg-attr.tag
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
ADDED public/riot-3.13.2/test/tag/table-data.tag
Index: public/riot-3.13.2/test/tag/table-data.tag
==================================================================
--- public/riot-3.13.2/test/tag/table-data.tag
+++ public/riot-3.13.2/test/tag/table-data.tag
@@ -0,0 +1,20 @@
+
+
+
+ Cells
+
+
+ Rows
+
+
+ { cell }
+ { cell } another
+
+
+
+ this.rows = [{ cell: 'One'}, { cell: 'Two'}, { cell: 'Three'}]
+
+
ADDED public/riot-3.13.2/test/tag/table-loop-extra-row.tag
Index: public/riot-3.13.2/test/tag/table-loop-extra-row.tag
==================================================================
--- public/riot-3.13.2/test/tag/table-loop-extra-row.tag
+++ public/riot-3.13.2/test/tag/table-loop-extra-row.tag
@@ -0,0 +1,22 @@
+
+
+
+ Rows
+
+
+ Extra
+ Row1
+
+
+ { cell }
+ { cell } another
+
+
+ Extra
+ Row2
+
+
+
+ this.rows = [{ cell: 'One'}, { cell: 'Two'}, { cell: 'Three'}]
+
+
ADDED public/riot-3.13.2/test/tag/table-multibody.tag
Index: public/riot-3.13.2/test/tag/table-multibody.tag
==================================================================
--- public/riot-3.13.2/test/tag/table-multibody.tag
+++ public/riot-3.13.2/test/tag/table-multibody.tag
@@ -0,0 +1,15 @@
+
+ Bodies
+
+ Swap color
+
+ this.bodies = [['A1', 'A2', 'A3'], ['B1', 'B2', 'B3'], ['C1', 'C2', 'C3']]
+ this.bgcolor = [ 'white', 'lime' ]
+ swapColor(i) {
+ this.bgcolor.reverse()
+ }
+
ADDED public/riot-3.13.2/test/tag/table-test.tag
Index: public/riot-3.13.2/test/tag/table-test.tag
==================================================================
--- public/riot-3.13.2/test/tag/table-test.tag
+++ public/riot-3.13.2/test/tag/table-test.tag
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Title
+
+
+
+
+
+ { cell }
+
+
+
+
+
+
+
+
+
+ { cell }
+
+
+
+ { cell }
+
+
+
+
+
+
+
+ { cell }
+
+
+
+ { cell }
+
+
+
+
+
+
+
+
+
+ { cell }
+
+
+
+ { cell }
+
+
+
+
+
+
+
+ { cell }
+
+
+
+ { cell }
+
+
+
+
+
+
+
+ { cell }
+
+
+
+
+
+ R1-C1 R1-C2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ { cell }
+
+
+ { cell }
+
+
+
+ { cell }
+
+
ADDED public/riot-3.13.2/test/tag/table-thead-tfoot-nested.tag
Index: public/riot-3.13.2/test/tag/table-thead-tfoot-nested.tag
==================================================================
--- public/riot-3.13.2/test/tag/table-thead-tfoot-nested.tag
+++ public/riot-3.13.2/test/tag/table-thead-tfoot-nested.tag
@@ -0,0 +1,9 @@
+
+
+
+
+ this.header = [{ cell: 'One'}, { cell: 'Two'}, { cell: 'Three'}]
+ this.footer = [{ cell: 'One'}, { cell: 'Two'}, { cell: 'Three'}]
+ this.rows = [['One'], ['Two'], ['Three']]
+
+
ADDED public/riot-3.13.2/test/tag/table-thead-tfoot.tag
Index: public/riot-3.13.2/test/tag/table-thead-tfoot.tag
==================================================================
--- public/riot-3.13.2/test/tag/table-thead-tfoot.tag
+++ public/riot-3.13.2/test/tag/table-thead-tfoot.tag
@@ -0,0 +1,19 @@
+
+
+
+ { cell }
+
+
+ { cell }
+
+
+
+ { cell }
+
+
+
+ this.header = opts.header
+ this.footer = opts.footer
+ this.rows = opts.rows
+
+
ADDED public/riot-3.13.2/test/tag/tag-nesting.tag
Index: public/riot-3.13.2/test/tag/tag-nesting.tag
==================================================================
--- public/riot-3.13.2/test/tag/tag-nesting.tag
+++ public/riot-3.13.2/test/tag/tag-nesting.tag
@@ -0,0 +1,40 @@
+
+
+
+
+
+ this.foo = { value: 10 }
+ this.bar = { value: 25 }
+
+ this.on('mount', function() {
+ this.tags.inner1.echo()
+ })
+
+ setTimeout(function() {
+ this.foo.value = 30
+ this.bar.value = 45
+ this.update()
+
+ }.bind(this), 600)
+
+
+
+
+
+ Inner1 foo: { opts.foo.value }
+ Inner1 bar: { opts.bar.value }
+
+
+
+ var bar = opts.bar.value
+
+ echo() {
+ this.refs.test.innerHTML = '+ECHOED+'
+ }
+
+
+
+
+ Inner2: { opts.bar.value + 50 }
+
+
ADDED public/riot-3.13.2/test/tag/textarea-value.tag
Index: public/riot-3.13.2/test/tag/textarea-value.tag
==================================================================
--- public/riot-3.13.2/test/tag/textarea-value.tag
+++ public/riot-3.13.2/test/tag/textarea-value.tag
@@ -0,0 +1,10 @@
+
+
+ click
+
+ this.message = 'hello there'
+ hello() {
+ this.message = 'world'
+ }
+
+
ADDED public/riot-3.13.2/test/tag/timer.tag
Index: public/riot-3.13.2/test/tag/timer.tag
==================================================================
--- public/riot-3.13.2/test/tag/timer.tag
+++ public/riot-3.13.2/test/tag/timer.tag
@@ -0,0 +1,24 @@
+
+
+ Seconds Elapsed: { time }
+
+ this.time = opts.start || 0
+
+ tick() {
+
+ this.update({ time: ++this.time })
+
+ if (this.opts.ontick) {
+ this.opts.ontick(this.time)
+ }
+
+ }
+
+ var timer = setInterval(this.tick, 1000)
+
+ this.on('unmount', function() {
+
+ clearInterval(timer)
+ })
+
+
ADDED public/riot-3.13.2/test/tag/timetable.tag
Index: public/riot-3.13.2/test/tag/timetable.tag
==================================================================
--- public/riot-3.13.2/test/tag/timetable.tag
+++ public/riot-3.13.2/test/tag/timetable.tag
@@ -0,0 +1,11 @@
+
+ { opts.baz } { bar }
+ this.bar = "romutus"
+
+
+
+
+ { kama }
+ this.times = [ 1, 3, 5 ]
+ this.kama = "jooo"
+
ADDED public/riot-3.13.2/test/tag/top-attributes.tag
Index: public/riot-3.13.2/test/tag/top-attributes.tag
==================================================================
--- public/riot-3.13.2/test/tag/top-attributes.tag
+++ public/riot-3.13.2/test/tag/top-attributes.tag
@@ -0,0 +1,5 @@
+
+ this.cls = opts.cls
+ this.s = "2em"
+ this.q = "quotes"
+
ADDED public/riot-3.13.2/test/tag/touch-events.tag
Index: public/riot-3.13.2/test/tag/touch-events.tag
==================================================================
--- public/riot-3.13.2/test/tag/touch-events.tag
+++ public/riot-3.13.2/test/tag/touch-events.tag
@@ -0,0 +1,18 @@
+
+
+
+
+
Touch me, touch me.
+
+
+
+
+ fn(e) {
+ this.info.innerHTML += e.type + ' '
+ }
+
+ clear() {
+ this.info.innerHTML = ''
+ }
+
+
ADDED public/riot-3.13.2/test/tag/treeview.tag
Index: public/riot-3.13.2/test/tag/treeview.tag
==================================================================
--- public/riot-3.13.2/test/tag/treeview.tag
+++ public/riot-3.13.2/test/tag/treeview.tag
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+ { name }
+ [{open ? '-' : '+'}]
+
+
+
+
+
+
+
+
ADDED public/riot-3.13.2/test/tag/update-context.tag
Index: public/riot-3.13.2/test/tag/update-context.tag
==================================================================
--- public/riot-3.13.2/test/tag/update-context.tag
+++ public/riot-3.13.2/test/tag/update-context.tag
@@ -0,0 +1,13 @@
+
+ { message }
+
+
+
ADDED public/riot-3.13.2/test/tag/v-dom-1.tag
Index: public/riot-3.13.2/test/tag/v-dom-1.tag
==================================================================
--- public/riot-3.13.2/test/tag/v-dom-1.tag
+++ public/riot-3.13.2/test/tag/v-dom-1.tag
@@ -0,0 +1,5 @@
+
+
+
+
+
ADDED public/riot-3.13.2/test/tag/v-dom-2.tag
Index: public/riot-3.13.2/test/tag/v-dom-2.tag
==================================================================
--- public/riot-3.13.2/test/tag/v-dom-2.tag
+++ public/riot-3.13.2/test/tag/v-dom-2.tag
@@ -0,0 +1,5 @@
+
+
+
+
+
ADDED public/riot-3.13.2/test/tag/v-dom-3.tag
Index: public/riot-3.13.2/test/tag/v-dom-3.tag
==================================================================
--- public/riot-3.13.2/test/tag/v-dom-3.tag
+++ public/riot-3.13.2/test/tag/v-dom-3.tag
@@ -0,0 +1,5 @@
+
+
+
+
+
ADDED public/riot-3.13.2/test/tag/virtual-conditional.tag
Index: public/riot-3.13.2/test/tag/virtual-conditional.tag
==================================================================
--- public/riot-3.13.2/test/tag/virtual-conditional.tag
+++ public/riot-3.13.2/test/tag/virtual-conditional.tag
@@ -0,0 +1,21 @@
+
+
+ { user.name }
+
+
+
+
+
+
+
+ hi
+
+
+
ADDED public/riot-3.13.2/test/tag/virtual-nested-component.tag
Index: public/riot-3.13.2/test/tag/virtual-nested-component.tag
==================================================================
--- public/riot-3.13.2/test/tag/virtual-nested-component.tag
+++ public/riot-3.13.2/test/tag/virtual-nested-component.tag
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+ { opts.name } - { opts.age }
+
ADDED public/riot-3.13.2/test/tag/virtual-nested-unmount.tag
Index: public/riot-3.13.2/test/tag/virtual-nested-unmount.tag
==================================================================
--- public/riot-3.13.2/test/tag/virtual-nested-unmount.tag
+++ public/riot-3.13.2/test/tag/virtual-nested-unmount.tag
@@ -0,0 +1,18 @@
+
+ {title}
+
+ {value}
+
+
+
+ updateChildren
+ var self = this;
+
+ self.childItems = [
+ {title:"1", childchildItems: ['1']},
+ {title:"2", childchildItems: ['1','2']},
+ {title:"3", childchildItems: ['1','2','3']}
+ ]
+
+ self.childItems
+
ADDED public/riot-3.13.2/test/tag/virtual-no-loop.tag
Index: public/riot-3.13.2/test/tag/virtual-no-loop.tag
==================================================================
--- public/riot-3.13.2/test/tag/virtual-no-loop.tag
+++ public/riot-3.13.2/test/tag/virtual-no-loop.tag
@@ -0,0 +1,19 @@
+
+
+ yielded text
+
+ {parent.message}
+
+ { parent.message }
+
+ {attr}
+
+ this.message = 'hello there'
+ this.attr = 'text'
+
+
+
+
+ {opts.msg}
+
+
ADDED public/riot-3.13.2/test/tag/virtual-yield-loop.tag
Index: public/riot-3.13.2/test/tag/virtual-yield-loop.tag
==================================================================
--- public/riot-3.13.2/test/tag/virtual-yield-loop.tag
+++ public/riot-3.13.2/test/tag/virtual-yield-loop.tag
@@ -0,0 +1,14 @@
+
+ { item.v }
+
+ this.items = [
+ {v: 'one'},
+ {v: 'two'},
+ {v: 'three'}
+ ]
+
+
+
+ take up space
+
+
ADDED public/riot-3.13.2/test/tag/yield-empty.tag
Index: public/riot-3.13.2/test/tag/yield-empty.tag
==================================================================
--- public/riot-3.13.2/test/tag/yield-empty.tag
+++ public/riot-3.13.2/test/tag/yield-empty.tag
@@ -0,0 +1,3 @@
+
+
+
ADDED public/riot-3.13.2/test/tag/yield-from-default.tag
Index: public/riot-3.13.2/test/tag/yield-from-default.tag
==================================================================
--- public/riot-3.13.2/test/tag/yield-from-default.tag
+++ public/riot-3.13.2/test/tag/yield-from-default.tag
@@ -0,0 +1,5 @@
+
+ (no options)
+ Hi
+ }>{ item }
+
ADDED public/riot-3.13.2/test/tag/yield-multi.tag
Index: public/riot-3.13.2/test/tag/yield-multi.tag
==================================================================
--- public/riot-3.13.2/test/tag/yield-multi.tag
+++ public/riot-3.13.2/test/tag/yield-multi.tag
@@ -0,0 +1,7 @@
+
+ yield the here
+
+
yield the nested here
+
do not yield the unreference content here
+
+
ADDED public/riot-3.13.2/test/tag/yield-multi2.tag
Index: public/riot-3.13.2/test/tag/yield-multi2.tag
==================================================================
--- public/riot-3.13.2/test/tag/yield-multi2.tag
+++ public/riot-3.13.2/test/tag/yield-multi2.tag
@@ -0,0 +1,5 @@
+
+
+
+
+
ADDED public/riot-3.13.2/test/tag/yield-nested.tag
Index: public/riot-3.13.2/test/tag/yield-nested.tag
==================================================================
--- public/riot-3.13.2/test/tag/yield-nested.tag
+++ public/riot-3.13.2/test/tag/yield-nested.tag
@@ -0,0 +1,75 @@
+
+
+ Greeting
+
+ this.greeting = 'from the child'
+
+
+
+
+
+ Greeting { this.parent.greeting }
+ { opts.subtitle }
+
+ this.greeting = 'from the child'
+
+
+
+
+ Hello,
+
+
+ { greeting }
+
+ wooha
+
+
+
+ this.greeting = 'from the parent'
+
+ saySomething() {
+
+ this.greeting = 'I am alive!'
+
+ if (opts.saySomething)
+ opts.saySomething()
+
+ this.update()
+
+ }.bind(this)
+
+
+
+
+ Hello,
+
+
+ { greeting }
+
+ wooha
+
+
+
+ this.greeting = 'from the parent'
+ this.items = [
+ { name: "subtitle1" },
+ { name: "subtitle2" },
+ { name: "subtitle3" },
+ { name: "subtitle4" },
+ { name: "subtitle5" }
+ ]
+
+ saySomething() {
+
+ this.greeting = 'I am alive!'
+
+ if (opts.saySomething)
+ opts.saySomething()
+
+ this.update()
+
+ }.bind(this)
+
+
+
+
ADDED public/riot-3.13.2/test/tag/yield-no-slash.tag
Index: public/riot-3.13.2/test/tag/yield-no-slash.tag
==================================================================
--- public/riot-3.13.2/test/tag/yield-no-slash.tag
+++ public/riot-3.13.2/test/tag/yield-no-slash.tag
@@ -0,0 +1,3 @@
+
+
+
ADDED public/riot-3.13.2/test/tag/~custom-parsers.tag
Index: public/riot-3.13.2/test/tag/~custom-parsers.tag
==================================================================
--- public/riot-3.13.2/test/tag/~custom-parsers.tag
+++ public/riot-3.13.2/test/tag/~custom-parsers.tag
@@ -0,0 +1,9 @@
+
+ hi
+
+
+
ADDED public/riot-3.13.2/test/tag/~import-tags.tag
Index: public/riot-3.13.2/test/tag/~import-tags.tag
==================================================================
--- public/riot-3.13.2/test/tag/~import-tags.tag
+++ public/riot-3.13.2/test/tag/~import-tags.tag
@@ -0,0 +1,4 @@
+require('./timer.tag')
+
+
+
ADDED public/riot-3.13.2/test/tag/~style-tag3.tag
Index: public/riot-3.13.2/test/tag/~style-tag3.tag
==================================================================
--- public/riot-3.13.2/test/tag/~style-tag3.tag
+++ public/riot-3.13.2/test/tag/~style-tag3.tag
@@ -0,0 +1,6 @@
+
+
+
+
ADDED public/riot-3.13.2/test/tags.html
Index: public/riot-3.13.2/test/tags.html
==================================================================
--- public/riot-3.13.2/test/tags.html
+++ public/riot-3.13.2/test/tags.html
@@ -0,0 +1,87 @@
+
+
+
+ Riot tag tests
+
+
+
+
+
+
+
+
+
+
+
+
+
ADDED public/riot-3.13.2/test/vendor/require.js
Index: public/riot-3.13.2/test/vendor/require.js
==================================================================
--- public/riot-3.13.2/test/vendor/require.js
+++ public/riot-3.13.2/test/vendor/require.js
@@ -0,0 +1,36 @@
+/*
+ RequireJS 2.1.18 Copyright (c) 2010-2015, The Dojo Foundation All Rights Reserved.
+ Available via the MIT or new BSD license.
+ see: http://github.com/jrburke/requirejs for details
+*/
+var requirejs,require,define;
+(function(ba){function G(b){return"[object Function]"===K.call(b)}function H(b){return"[object Array]"===K.call(b)}function v(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(G(l)){if(this.events.error&&this.map.isDefine||g.onError!==ca)try{f=i.execCb(c,l,b,f)}catch(d){a=d}else f=i.execCb(c,l,b,f);this.map.isDefine&&void 0===f&&((b=this.module)?f=b.exports:this.usingExports&&
+(f=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",w(this.error=a)}else f=l;this.exports=f;if(this.map.isDefine&&!this.ignore&&(r[c]=f,g.onResourceLoad))g.onResourceLoad(i,this.map,this.depMaps);y(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=
+this.map,b=a.id,d=p(a.prefix);this.depMaps.push(d);q(d,"defined",u(this,function(f){var l,d;d=m(aa,this.map.id);var e=this.map.name,P=this.map.parentMap?this.map.parentMap.name:null,n=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(e=f.normalize(e,function(a){return c(a,P,!0)})||""),f=p(a.prefix+"!"+e,this.map.parentMap),q(f,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=m(h,f.id)){this.depMaps.push(f);
+if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else d?(this.map.url=i.nameToUrl(d),this.load()):(l=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),l.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];B(h,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&y(a.map.id)});w(a)}),l.fromText=u(this,function(f,c){var d=a.name,e=p(d),P=M;c&&(f=c);P&&(M=!1);s(e);t(j.config,b)&&(j.config[d]=j.config[b]);try{g.exec(f)}catch(h){return w(C("fromtexteval",
+"fromText eval for "+b+" failed: "+h,h,[b]))}P&&(M=!0);this.depMaps.push(e);i.completeLoad(d);n([d],l)}),f.load(a.name,n,l,j))}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){V[this.map.id]=this;this.enabling=this.enabled=!0;v(this.depMaps,u(this,function(a,b){var c,f;if("string"===typeof a){a=p(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=m(L,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;q(a,"defined",u(this,function(a){this.undefed||
+(this.defineDep(b,a),this.check())}));this.errback?q(a,"error",u(this,this.errback)):this.events.error&&q(a,"error",u(this,function(a){this.emit("error",a)}))}c=a.id;f=h[c];!t(L,c)&&(f&&!f.enabled)&&i.enable(a,this)}));B(this.pluginMaps,u(this,function(a){var b=m(h,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){v(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};
+i={config:j,contextName:b,registry:h,defined:r,urlFetched:S,defQueue:A,Module:Z,makeModuleMap:p,nextTick:g.nextTick,onError:w,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=j.shim,c={paths:!0,bundles:!0,config:!0,map:!0};B(a,function(a,b){c[b]?(j[b]||(j[b]={}),U(j[b],a,!0,!0)):j[b]=a});a.bundles&&B(a.bundles,function(a,b){v(a,function(a){a!==b&&(aa[a]=b)})});a.shim&&(B(a.shim,function(a,c){H(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=
+i.makeShimExports(a);b[c]=a}),j.shim=b);a.packages&&v(a.packages,function(a){var b,a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(j.paths[b]=a.location);j.pkgs[b]=a.name+"/"+(a.main||"main").replace(ia,"").replace(Q,"")});B(h,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=p(b,null,!0))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(ba,arguments));return b||a.exports&&da(a.exports)}},makeRequire:function(a,
+e){function j(c,d,m){var n,q;e.enableBuildCallback&&(d&&G(d))&&(d.__requireJsBuild=!0);if("string"===typeof c){if(G(d))return w(C("requireargs","Invalid require call"),m);if(a&&t(L,c))return L[c](h[a.id]);if(g.get)return g.get(i,c,a,j);n=p(c,a,!1,!0);n=n.id;return!t(r,n)?w(C("notloaded",'Module name "'+n+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):r[n]}J();i.nextTick(function(){J();q=s(p(null,a));q.skipMap=e.skipMap;q.init(c,d,m,{enabled:!0});D()});return j}e=e||{};U(j,
+{isBrowser:z,toUrl:function(b){var d,e=b.lastIndexOf("."),k=b.split("/")[0];if(-1!==e&&(!("."===k||".."===k)||1e.attachEvent.toString().indexOf("[native code"))&&!Y?(M=!0,e.attachEvent("onreadystatechange",b.onScriptLoad)):(e.addEventListener("load",b.onScriptLoad,!1),e.addEventListener("error",b.onScriptError,!1)),e.src=d,J=e,D?y.insertBefore(e,D):y.appendChild(e),J=null,e;if(ea)try{importScripts(d),b.completeLoad(c)}catch(m){b.onError(C("importscripts","importScripts failed for "+c+" at "+d,m,[c]))}};z&&!q.skipDataMain&&T(document.getElementsByTagName("script"),function(b){y||(y=b.parentNode);if(I=b.getAttribute("data-main"))return s=
+I,q.baseUrl||(E=s.split("/"),s=E.pop(),O=E.length?E.join("/")+"/":"./",q.baseUrl=O),s=s.replace(Q,""),g.jsExtRegExp.test(s)&&(s=I),q.deps=q.deps?q.deps.concat(s):[s],!0});define=function(b,c,d){var e,g;"string"!==typeof b&&(d=c,c=b,b=null);H(c)||(d=c,c=null);!c&&G(d)&&(c=[],d.length&&(d.toString().replace(ka,"").replace(la,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(M){if(!(e=J))N&&"interactive"===N.readyState||T(document.getElementsByTagName("script"),
+function(b){if("interactive"===b.readyState)return N=b}),e=N;e&&(b||(b=e.getAttribute("data-requiremodule")),g=F[e.getAttribute("data-requirecontext")])}(g?g.defQueue:R).push([b,c,d])};define.amd={jQuery:!0};g.exec=function(b){return eval(b)};g(q)}})(this);
ADDED public/samples/bd01.wav
Index: public/samples/bd01.wav
==================================================================
--- public/samples/bd01.wav
+++ public/samples/bd01.wav
cannot compute difference between binary files
ADDED public/samples/bd02.wav
Index: public/samples/bd02.wav
==================================================================
--- public/samples/bd02.wav
+++ public/samples/bd02.wav
cannot compute difference between binary files
ADDED public/samples/bd03.wav
Index: public/samples/bd03.wav
==================================================================
--- public/samples/bd03.wav
+++ public/samples/bd03.wav
cannot compute difference between binary files
ADDED public/samples/bd04.wav
Index: public/samples/bd04.wav
==================================================================
--- public/samples/bd04.wav
+++ public/samples/bd04.wav
cannot compute difference between binary files
ADDED public/samples/bd05.wav
Index: public/samples/bd05.wav
==================================================================
--- public/samples/bd05.wav
+++ public/samples/bd05.wav
cannot compute difference between binary files
ADDED public/samples/bd06.wav
Index: public/samples/bd06.wav
==================================================================
--- public/samples/bd06.wav
+++ public/samples/bd06.wav
cannot compute difference between binary files
ADDED public/samples/bd07.wav
Index: public/samples/bd07.wav
==================================================================
--- public/samples/bd07.wav
+++ public/samples/bd07.wav
cannot compute difference between binary files
ADDED public/samples/bd08.wav
Index: public/samples/bd08.wav
==================================================================
--- public/samples/bd08.wav
+++ public/samples/bd08.wav
cannot compute difference between binary files
ADDED public/samples/bd09.wav
Index: public/samples/bd09.wav
==================================================================
--- public/samples/bd09.wav
+++ public/samples/bd09.wav
cannot compute difference between binary files
ADDED public/samples/bd10.wav
Index: public/samples/bd10.wav
==================================================================
--- public/samples/bd10.wav
+++ public/samples/bd10.wav
cannot compute difference between binary files
ADDED public/samples/cp01.wav
Index: public/samples/cp01.wav
==================================================================
--- public/samples/cp01.wav
+++ public/samples/cp01.wav
cannot compute difference between binary files
ADDED public/samples/cp02.wav
Index: public/samples/cp02.wav
==================================================================
--- public/samples/cp02.wav
+++ public/samples/cp02.wav
cannot compute difference between binary files
ADDED public/samples/cp03.wav
Index: public/samples/cp03.wav
==================================================================
--- public/samples/cp03.wav
+++ public/samples/cp03.wav
cannot compute difference between binary files
ADDED public/samples/cp04.wav
Index: public/samples/cp04.wav
==================================================================
--- public/samples/cp04.wav
+++ public/samples/cp04.wav
cannot compute difference between binary files
ADDED public/samples/cr01.wav
Index: public/samples/cr01.wav
==================================================================
--- public/samples/cr01.wav
+++ public/samples/cr01.wav
cannot compute difference between binary files
ADDED public/samples/cr02.wav
Index: public/samples/cr02.wav
==================================================================
--- public/samples/cr02.wav
+++ public/samples/cr02.wav
cannot compute difference between binary files
ADDED public/samples/cr03.wav
Index: public/samples/cr03.wav
==================================================================
--- public/samples/cr03.wav
+++ public/samples/cr03.wav
cannot compute difference between binary files
ADDED public/samples/cr04.wav
Index: public/samples/cr04.wav
==================================================================
--- public/samples/cr04.wav
+++ public/samples/cr04.wav
cannot compute difference between binary files
ADDED public/samples/hh01.wav
Index: public/samples/hh01.wav
==================================================================
--- public/samples/hh01.wav
+++ public/samples/hh01.wav
cannot compute difference between binary files
ADDED public/samples/hh02.wav
Index: public/samples/hh02.wav
==================================================================
--- public/samples/hh02.wav
+++ public/samples/hh02.wav
cannot compute difference between binary files
ADDED public/samples/hh03.wav
Index: public/samples/hh03.wav
==================================================================
--- public/samples/hh03.wav
+++ public/samples/hh03.wav
cannot compute difference between binary files
ADDED public/samples/hh04.wav
Index: public/samples/hh04.wav
==================================================================
--- public/samples/hh04.wav
+++ public/samples/hh04.wav
cannot compute difference between binary files
ADDED public/samples/ht01.wav
Index: public/samples/ht01.wav
==================================================================
--- public/samples/ht01.wav
+++ public/samples/ht01.wav
cannot compute difference between binary files
ADDED public/samples/ht02.wav
Index: public/samples/ht02.wav
==================================================================
--- public/samples/ht02.wav
+++ public/samples/ht02.wav
cannot compute difference between binary files
ADDED public/samples/ht03.wav
Index: public/samples/ht03.wav
==================================================================
--- public/samples/ht03.wav
+++ public/samples/ht03.wav
cannot compute difference between binary files
ADDED public/samples/ht04.wav
Index: public/samples/ht04.wav
==================================================================
--- public/samples/ht04.wav
+++ public/samples/ht04.wav
cannot compute difference between binary files
ADDED public/samples/ht05.wav
Index: public/samples/ht05.wav
==================================================================
--- public/samples/ht05.wav
+++ public/samples/ht05.wav
cannot compute difference between binary files
ADDED public/samples/ht06.wav
Index: public/samples/ht06.wav
==================================================================
--- public/samples/ht06.wav
+++ public/samples/ht06.wav
cannot compute difference between binary files
ADDED public/samples/ht07.wav
Index: public/samples/ht07.wav
==================================================================
--- public/samples/ht07.wav
+++ public/samples/ht07.wav
cannot compute difference between binary files
ADDED public/samples/ht08.wav
Index: public/samples/ht08.wav
==================================================================
--- public/samples/ht08.wav
+++ public/samples/ht08.wav
cannot compute difference between binary files
ADDED public/samples/lt01.wav
Index: public/samples/lt01.wav
==================================================================
--- public/samples/lt01.wav
+++ public/samples/lt01.wav
cannot compute difference between binary files
ADDED public/samples/lt02.wav
Index: public/samples/lt02.wav
==================================================================
--- public/samples/lt02.wav
+++ public/samples/lt02.wav
cannot compute difference between binary files
ADDED public/samples/lt03.wav
Index: public/samples/lt03.wav
==================================================================
--- public/samples/lt03.wav
+++ public/samples/lt03.wav
cannot compute difference between binary files
ADDED public/samples/lt04.wav
Index: public/samples/lt04.wav
==================================================================
--- public/samples/lt04.wav
+++ public/samples/lt04.wav
cannot compute difference between binary files
ADDED public/samples/lt05.wav
Index: public/samples/lt05.wav
==================================================================
--- public/samples/lt05.wav
+++ public/samples/lt05.wav
cannot compute difference between binary files
ADDED public/samples/lt06.wav
Index: public/samples/lt06.wav
==================================================================
--- public/samples/lt06.wav
+++ public/samples/lt06.wav
cannot compute difference between binary files
ADDED public/samples/lt07.wav
Index: public/samples/lt07.wav
==================================================================
--- public/samples/lt07.wav
+++ public/samples/lt07.wav
cannot compute difference between binary files
ADDED public/samples/lt08.wav
Index: public/samples/lt08.wav
==================================================================
--- public/samples/lt08.wav
+++ public/samples/lt08.wav
cannot compute difference between binary files
ADDED public/samples/mt01.wav
Index: public/samples/mt01.wav
==================================================================
--- public/samples/mt01.wav
+++ public/samples/mt01.wav
cannot compute difference between binary files
ADDED public/samples/mt02.wav
Index: public/samples/mt02.wav
==================================================================
--- public/samples/mt02.wav
+++ public/samples/mt02.wav
cannot compute difference between binary files
ADDED public/samples/mt03.wav
Index: public/samples/mt03.wav
==================================================================
--- public/samples/mt03.wav
+++ public/samples/mt03.wav
cannot compute difference between binary files
ADDED public/samples/mt04.wav
Index: public/samples/mt04.wav
==================================================================
--- public/samples/mt04.wav
+++ public/samples/mt04.wav
cannot compute difference between binary files
ADDED public/samples/mt05.wav
Index: public/samples/mt05.wav
==================================================================
--- public/samples/mt05.wav
+++ public/samples/mt05.wav
cannot compute difference between binary files
ADDED public/samples/mt06.wav
Index: public/samples/mt06.wav
==================================================================
--- public/samples/mt06.wav
+++ public/samples/mt06.wav
cannot compute difference between binary files
ADDED public/samples/mt07.wav
Index: public/samples/mt07.wav
==================================================================
--- public/samples/mt07.wav
+++ public/samples/mt07.wav
cannot compute difference between binary files
ADDED public/samples/mt08.wav
Index: public/samples/mt08.wav
==================================================================
--- public/samples/mt08.wav
+++ public/samples/mt08.wav
cannot compute difference between binary files
ADDED public/samples/oh01.wav
Index: public/samples/oh01.wav
==================================================================
--- public/samples/oh01.wav
+++ public/samples/oh01.wav
cannot compute difference between binary files
ADDED public/samples/oh02.wav
Index: public/samples/oh02.wav
==================================================================
--- public/samples/oh02.wav
+++ public/samples/oh02.wav
cannot compute difference between binary files
ADDED public/samples/oh03.wav
Index: public/samples/oh03.wav
==================================================================
--- public/samples/oh03.wav
+++ public/samples/oh03.wav
cannot compute difference between binary files
ADDED public/samples/oh04.wav
Index: public/samples/oh04.wav
==================================================================
--- public/samples/oh04.wav
+++ public/samples/oh04.wav
cannot compute difference between binary files
ADDED public/samples/rd01.wav
Index: public/samples/rd01.wav
==================================================================
--- public/samples/rd01.wav
+++ public/samples/rd01.wav
cannot compute difference between binary files
ADDED public/samples/rd02.wav
Index: public/samples/rd02.wav
==================================================================
--- public/samples/rd02.wav
+++ public/samples/rd02.wav
cannot compute difference between binary files
ADDED public/samples/rd03.wav
Index: public/samples/rd03.wav
==================================================================
--- public/samples/rd03.wav
+++ public/samples/rd03.wav
cannot compute difference between binary files
ADDED public/samples/rd04.wav
Index: public/samples/rd04.wav
==================================================================
--- public/samples/rd04.wav
+++ public/samples/rd04.wav
cannot compute difference between binary files
ADDED public/samples/rs01.wav
Index: public/samples/rs01.wav
==================================================================
--- public/samples/rs01.wav
+++ public/samples/rs01.wav
cannot compute difference between binary files
ADDED public/samples/rs02.wav
Index: public/samples/rs02.wav
==================================================================
--- public/samples/rs02.wav
+++ public/samples/rs02.wav
cannot compute difference between binary files
ADDED public/samples/sd01.wav
Index: public/samples/sd01.wav
==================================================================
--- public/samples/sd01.wav
+++ public/samples/sd01.wav
cannot compute difference between binary files
ADDED public/samples/sd02.wav
Index: public/samples/sd02.wav
==================================================================
--- public/samples/sd02.wav
+++ public/samples/sd02.wav
cannot compute difference between binary files
ADDED public/samples/sd03.wav
Index: public/samples/sd03.wav
==================================================================
--- public/samples/sd03.wav
+++ public/samples/sd03.wav
cannot compute difference between binary files
ADDED public/samples/sd04.wav
Index: public/samples/sd04.wav
==================================================================
--- public/samples/sd04.wav
+++ public/samples/sd04.wav
cannot compute difference between binary files
ADDED public/samples/sd05.wav
Index: public/samples/sd05.wav
==================================================================
--- public/samples/sd05.wav
+++ public/samples/sd05.wav
cannot compute difference between binary files
ADDED public/samples/sd06.wav
Index: public/samples/sd06.wav
==================================================================
--- public/samples/sd06.wav
+++ public/samples/sd06.wav
cannot compute difference between binary files
ADDED public/samples/sd07.wav
Index: public/samples/sd07.wav
==================================================================
--- public/samples/sd07.wav
+++ public/samples/sd07.wav
cannot compute difference between binary files
ADDED public/samples/sd08.wav
Index: public/samples/sd08.wav
==================================================================
--- public/samples/sd08.wav
+++ public/samples/sd08.wav
cannot compute difference between binary files
ADDED public/samples/sd09.wav
Index: public/samples/sd09.wav
==================================================================
--- public/samples/sd09.wav
+++ public/samples/sd09.wav
cannot compute difference between binary files
ADDED public/samples/sd10.wav
Index: public/samples/sd10.wav
==================================================================
--- public/samples/sd10.wav
+++ public/samples/sd10.wav
cannot compute difference between binary files
ADDED public/samples/sd11.wav
Index: public/samples/sd11.wav
==================================================================
--- public/samples/sd11.wav
+++ public/samples/sd11.wav
cannot compute difference between binary files
ADDED public/samples/sd12.wav
Index: public/samples/sd12.wav
==================================================================
--- public/samples/sd12.wav
+++ public/samples/sd12.wav
cannot compute difference between binary files
ADDED public/samples/sd13.wav
Index: public/samples/sd13.wav
==================================================================
--- public/samples/sd13.wav
+++ public/samples/sd13.wav
cannot compute difference between binary files
ADDED public/samples/sd14.wav
Index: public/samples/sd14.wav
==================================================================
--- public/samples/sd14.wav
+++ public/samples/sd14.wav
cannot compute difference between binary files
ADDED public/samples/sd15.wav
Index: public/samples/sd15.wav
==================================================================
--- public/samples/sd15.wav
+++ public/samples/sd15.wav
cannot compute difference between binary files
ADDED public/teoria-master/LICENSE
Index: public/teoria-master/LICENSE
==================================================================
--- public/teoria-master/LICENSE
+++ public/teoria-master/LICENSE
@@ -0,0 +1,18 @@
+Copyright (c) 2015 Jakob Miland
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
ADDED public/teoria-master/README.md
Index: public/teoria-master/README.md
==================================================================
--- public/teoria-master/README.md
+++ public/teoria-master/README.md
@@ -0,0 +1,568 @@
+Teoria.js
+=========
+
+Teoria.js is a lightweight and fast JavaScript library
+for music theory, both Jazz and Classical. It aims at providing an intuitive
+programming interface for music software (such as Sheet Readers,
+Sheet Writers, MIDI Players etc.).
+
+Features
+---------
+
+ - A note object (`teoria.Note`), which understands alterations, octaves,
+ key number, frequency and etc. and Helmholtz notation
+
+ - A chord object (`teoria.Chord`), which understands everything
+ from simple major/minor chords to advanced Jazz chords (Ab#5b9, F(#11) and such)
+
+ - A scale object (`teoria.Scale`), The scale object is a powerful presentation of
+ a scale, which supports quite a few handy methods. A scale can either be
+ constructed from the predefined scales, which by default contains the 7 modes
+ (Ionian, Dorian, Phrygian etc.) a major and minor pentatonic and the harmonic
+ chromatic scale or from an arbitrary array of intervals. The scale object
+ also supports solfège, which makes it perfect for tutorials on sight-reading.
+
+ - An interval object (`teoria.Interval`), which makes it easy to find the
+ interval between two notes, or find a note that is a given interval from a note.
+ There's also support for counting the interval span in semitones and inverting the
+ interval.
+
+Usage
+--------
+
+ $ npm install teoria
+
+Can be used with both Node and Browserify/webpack/etc.
+
+### ... or less preferable
+
+Include the bundled build file, `teoria.js` from this repository, directly:
+```html
+
+```
+Syntax
+---------
+
+This is just a short introduction to what teoria-code looks like,
+for a technical library reference, look further down this document.
+
+```javascript
+
+// Create notes:
+var a4 = teoria.note('a4'); // Scientific notation
+var g5 = teoria.note("g''"); // Helmholtz notation
+var c3 = teoria.note.fromKey(28); // From a piano key number
+
+// Find and create notes based on intervals
+teoria.interval(a4, g5); // Returns a Interval object representing a minor seventh
+teoria.interval(a4, 'M6'); // Returns a Note representing F#5
+a4.interval('m3'); // Returns a Note representing C#4
+a4.interval(g5); // Returns a Interval object representing a minor seventh
+a4.interval(teoria.note('bb5')).invert(); // Returns a Interval representing a major seventh
+
+// Create scales, based on notes.
+a4.scale('mixolydian').simple(); // Returns: ["a", "b", "c#", "d", "e", "f#", "g"]
+a4.scale('aeolian').simple(); // Returns: ["a", "b", "c", "d", "e", "f", "g"]
+g5.scale('ionian').simple(); // Returns: ["g", "a", "b", "c", "d", "e", "f#"]
+g5.scale('dorian'); // Returns a Scale object
+
+// Create chords with the powerful chord parser
+a4.chord('sus2').name; // Returns the name of the chord: 'Asus2'
+c3.chord('m').name; // Returns 'Cm'
+teoria.chord('Ab#5b9'); // Returns a Chord object, representing a Ab#5b9 chord
+g5.chord('dim'); // Returns a Chord object, representing a Gdim chord
+
+// Calculate note frequencies or find the note corresponding to a frequency
+teoria.note.fromFrequency(467); // Returns: {'note':{...},'cents':3.102831} -> A4# a little out of tune.
+a4.fq(); // Outputs 440
+g5.fq(); // Outputs 783.9908719634985
+
+// teoria allows for crazy chaining:
+teoria.note('a') // Create a note, A3
+ .scale('lydian') // Create a lydian scale with that note as root (A lydian)
+ .interval('M2') // Transpose the whole scale a major second up (B lydian)
+ .get('third') // Get the third note of the scale (D#4)
+ .chord('maj9') // Create a maj9 chord with that note as root (D#maj9)
+ .toString(); // Make a string representation: 'D#maj9'
+```
+
+Documentation
+------------------------
+
+## teoria.note (name | coord[, duration])
+
+*name* - The name argument is the note name as a string. The note can both
+be expressed in scientific and Helmholtz notation.
+Some examples of valid note names: `Eb4`, `C#,,`, `C4`, `d#''`, `Ab2`
+
+*coord* - If the first argument isn't a string, but a coord array,
+it will instantiate a `Note` instance.
+
+*duration* - The duration argument is an optional `object` argument.
+The object has two also optional parameters:
+
+ - `value` - A `number` corresponding to the value of the duration, such that:
+`1 = whole`, `2 = half (minim)`, `4 = quarter`, `8 = eight`
+
+ - `dots` - The number of dots attached to the note. Defaults to `0`.
+
+### teoria.note.fromKey(key)
+A static method that returns an instance of Note set to the note
+at the given piano key, where A0 is key number 1.
+See [Wikipedia's piano key article](http://en.wikipedia.org/wiki/Piano_key_frequencies)
+for more information.
+
+### teoria.note.fromFrequency(fq)
+A static method returns an object containing two elements:
+
+*note* - A `Note` which corresponds to the closest note with the given frequency
+
+*cents* - A number value of how many cents the note is out of tune
+
+### teoria.note.fromMIDI(note)
+ - Returns an instance of Note set to the corresponding MIDI note value.
+
+*note* - A number ranging from 0-127 representing a MIDI note value
+
+### teoria.note.fromString(note)
+ - Returns an instance of Note representing the note name
+
+*note* - The name argument is the note name as a string. The note can both
+be expressed in scientific and Helmholtz notation.
+Some examples of valid note names: `Eb4`, `C#,,`, `C4`, `d#''`, `Ab2`
+
+#### Note.name()
+ - The name of the note, in lowercase letter (*only* the name, not the
+ accidental signs)
+
+#### Note.octave()
+ - The numeric value of the octave of the note
+
+#### Note.duration
+ - The duration object as described in the constructor for Note
+
+#### Note.accidental()
+ - Returns the string symbolic of the accidental sign (`x`, `#`, `b` or `bb`)
+
+#### Note.accidentalValue()
+ - Returns the numeric value (mostly used internally) of the sign:
+`x = 2, # = 1, b = -1, bb = -2`
+
+#### Note#key([whitenotes])
+ - Returns the piano key number. E.g. A4 would return 49
+
+*whitenotes* - If this parameter is set to `true` only the white keys will
+be counted when finding the key number. This is mostly for internal use.
+
+#### Note#midi()
+ - Returns a number ranging from 0-127 representing a MIDI note value
+
+#### Note#fq([concertPitch])
+ - Calculates and returns the frequency of the note.
+
+*concertPitch* - If supplied this number will be used instead of the normal
+concert pitch which is 440hz. This is useful for some classical music.
+
+#### Note#chroma()
+ - Returns the pitch class (index) of the note.
+
+This allows for easy enharmonic checking:
+
+ teoria.note('e').chroma() === teoria.note('fb').chroma();
+
+The chroma number is ranging from pitch class C which is 0 to 11 which is B
+
+#### Note#scale(scaleName)
+ - Returns an instance of Scale, with the tonic/root set to this note.
+
+*scaleName* - The name of the scale to be returned. `'minor'`,
+`'chromatic'`, `'ionian'` and others are valid scale names.
+
+#### Note#interval(interval)
+ - A sugar function for calling teoria.interval(note, interval);
+
+Look at the documentation for `teoria.interval`
+
+#### Note#transpose(interval)
+ - Like the `#interval` method, but changes `this` note, instead of returning a new
+
+#### Note#chord([name])
+ - Returns an instance of Chord, with root note set to this note
+
+*name* - The name attribute is the last part of the chord symbol.
+Examples: `'m7'`, `'#5b9'`, `'major'`. If the name parameter
+isn't set, a standard major chord will be returned.
+
+#### Note#helmholtz()
+ - Returns the note name formatted in Helmholtz notation.
+
+Example: `teoria.note('A5').helmholtz() -> "a''"`
+
+#### Note#scientific()
+ - Returns the note name formatted in scientific notation.
+
+Example: `teoria.note("ab'").scientific() -> "Ab4"`
+
+#### Note#enharmonics(oneAccidental)
+ - Returns all notes that are enharmonic with the note
+
+*oneAccidental* - Boolean, if set to true, only enharmonic notes with one
+accidental is returned. E.g. results such as 'eb' and 'c#' but not 'ebb' and 'cx'
+
+```javascript
+teoria.note('c').enharmonics().toString();
+// -> 'dbb, b#'
+
+teoria.note('c').enharmonics(true).toString();
+// -> 'b#'
+```
+
+#### Note#durationInSeconds(bpm, beatUnit)
+ - Returns the duration of the note, given a tempo (in bpm) and a beat unit
+ (the lower numeral of the time signature)
+
+#### Note#solfege(scale, showOctaves)
+ - Returns the solfege step in the given scale context
+
+*scale* - An instance of `Scale`, which is the context of the solfege step measuring
+
+*showOctaves* - A boolean. If set to true, a "Helmholtz-like" notation will be
+used if there's bigger intervals than an octave
+
+#### Note#durationName()
+ - Returns the duration name.
+
+Examples: `teoria.note('A', 8).durationName() -> 'eighth'`,
+`teoria.note('C', 16).durationName() -> 'sixteenth'`
+
+#### Note#scaleDegree(scale)
+ - Returns this note's degree in a given scale (Scale). For example a
+ `D` in a C major scale will return `2` as it is the second degree of that scale.
+ If however the note *isn't* a part of the scale, the degree returned will be
+ `0`, meaning that the degree doesn't exist. This allows this method to be both
+ a scale degree index finder *and* an "isNoteInScale" method.
+
+*scale* - An instance of `Scale` which is the context of the degree measuring
+
+#### Note#toString([dontShow])
+ - Usability function for returning the note as a string
+
+*dontShow* - If set to `true` the octave will not be included in the returned string.
+
+## Chord(root, chord)
+ - A chord class with a lot of functionality to alter and analyze the chord.
+
+*root* - A `Note` instance which is to be the root of the chord
+
+*chord* - A string containing the chord symbol. This can be anything from
+simple chords, to super-advanced jazz chords thanks to the detailed and
+robust chord parser engine. Example values:
+`'m'`, `'m7'`, `'#5b9'`, `'9sus4` and `'#11b5#9'`
+
+### teoria.chord(name || note[, octave || symbol])
+ - A simple function for getting the notes, no matter the octave, in a chord
+
+*name* - A string containing the full chord symbol, with note name. Examples:
+`'Ab7'`, `'F#(#11b5)'`
+
+*note* - Instead of supplying a string containing the full chord symbol,
+one can pass a `Note` object instead. The note will be considered root in
+the new chord object
+
+*octave* - If the first argument of the function is a chord name (`typeof "string"`),
+then the second argument is an optional octave number (`typeof "number"`) of the root.
+
+*symbol* - A string containing the chord symbol (excluding the note name)
+
+#### Chord.name
+ - Holds the full chord symbol, inclusive the root name.
+
+#### Chord.root
+ - Holds the `Note` that is the root of the chord.
+
+#### Chord#notes()
+ - Returns an array of `Note`s that the chord consists of.
+
+#### Chord#simple()
+ - Returns an `Array` of only the notes' names, not the full `Note` objects.
+
+#### Chord#bass()
+ - Returns the bass note of the chord (The note voiced the lowest)
+
+#### Chord#voicing([voicing])
+ - Works both as a setter and getter. If no parameter is supplied the
+ current voicing is returned as an array of `Interval`s
+
+*voicing* - An optional array of intervals in simple-format
+that represents the current voicing of the chord.
+
+Here's an example:
+```javascript
+var bbmaj = teoria.chord('Bbmaj7');
+// Default voicing:
+bbmaj.voicing(); // #-> ['P1', 'M3', 'P5', 'M7'];
+bbmaj.notes(); // #-> ['bb', 'd', 'f', 'a'];
+
+// New voicing
+bbmaj.voicing(['P1', 'P5', 'M7', 'M10']);
+bbmaj.notes(); // #-> ['bb', 'f', 'a', 'd'];
+```
+*NB:* Note that above returned results are pseudo-results, as they will be
+returned wrapped in `Interval` and `Note` objects.
+
+#### Chord#quality()
+ - Returns a string which holds the quality of the chord, `'major'`, `'minor'`,
+ `'augmented'`, `'diminished'`, `'half-diminished'`, `'dominant'` or `undefined`
+
+#### Chord#get(interval)
+ - Returns the note at a given interval in the chord, if it exists.
+
+*interval* - A string name of an interval, for example `'third'`, `'fifth'`, `'ninth'`.
+
+#### Chord#dominant([additional])
+ - Returns the naïvely chosen dominant which is a perfect fifth away.
+
+*additional* - Additional chord extension, for example: `'b9'` or `'#5'`
+
+#### Chord#subdominant([additional])
+ - Returns the naïvely chosen subdominant which is a perfect fourth away.
+
+*additional* - Like the dominant's.
+
+#### Chord#parallel([additional])
+ - Returns the parallel chord for major and minor triads
+
+*additional* - Like the dominant's
+
+#### Chord#chordType()
+ - Returns the type of the chord: `'dyad'`, `'triad'`, `'trichord'`,
+ `'tetrad'` or `'unknown'`.
+
+#### Chord#interval(interval)
+ - Returns the same chord, a `interval` away
+
+#### Chord#transpose(interval)
+ - Like the `#interval` method, except it's `this` chord that gets changed instead of
+ returning a new chord.
+
+#### Chord#toString()
+ - Simple usability function which is an alias for Chord.name
+
+
+## Scale(tonic, scale)
+ - The teoria representation of a scale, with a given tonic.
+
+*tonic* - A `Note` which is to be the tonic of the scale
+
+*scale* - Can either be a name of a scale (string), or an array of
+absolute intervals that defines the scale. The scales supported by default are:
+
+ - major
+ - minor
+ - ionian (Alias for major)
+ - dorian
+ - phrygian
+ - lydian
+ - mixolydian
+ - aeolian (Alias for minor)
+ - locrian
+ - majorpentatonic
+ - minorpentatonic
+ - chromatic
+ - harmonicchromatic (Alias for chromatic)
+ - blues
+ - doubleharmonic
+ - flamenco
+ - harmonicminor
+ - melodicminor
+ - wholetone
+
+### teoria.scale(tonic, scale)
+ - Sugar function for constructing a new `Scale` object
+
+#### teoria.Scale.KNOWN_SCALES
+ - An array of all the scale ID's that comes with teoria
+
+#### Scale.name
+ - The name of the scale (if available). Type `string` or `undefined`
+
+#### Scale.tonic
+ - The `Note` which is the scale's tonic
+
+#### Scale#notes()
+ - Returns an array of `Note`s which is the scale's notes
+
+#### Scale#simple()
+ - Returns an `Array` of only the notes' names, not the full `Note` objects.
+
+#### Scale#type()
+ - Returns the type of the scale, depending on the number of notes.
+ A scale of length x gives y:
+ - 2 gives 'ditonic'
+ - 3 gives 'tritonic'
+ - 4 gives 'tetratonic'
+ - 5 gives 'pentatonic'
+ - 6 gives 'hexatonic',
+ - 7 gives 'heptatonic',
+ - 8 gives 'octatonic'
+
+#### Scale#get(index)
+ - Returns the note at the given scale index
+
+*index* - Can be a number referring to the scale step, or the name (string) of the
+scale step. E.g. 'first', 'second', 'fourth', 'seventh'.
+
+#### Scale#solfege(index, showOctaves)
+ - Returns the solfege name of the given scale step
+
+*index* Same as `Scale#get`
+
+*showOctaves* - A boolean meaning the same as `showOctaves` in `Note#solfege`
+
+
+## teoria.interval(from, to)
+ - A sugar function for the `#from` and `#between` methods of the same namespace and
+ for creating `Interval` objects.
+
+#### teoria.interval(`string`: from)
+ - A sugar method for the `Interval.toCoord` function
+
+#### teoria.interval(`Note`: from, `string`: to)
+ - A sugar method for the `Interval.from` function
+
+#### teoria.interval(`Note`: from, `Interval`: to)
+ - Like above, but with a `Interval` instead of a string representation of
+ the interval
+
+#### teoria.interval(`Note`: from, `Note`: to)
+ - A sugar method for the `Interval.between` function
+
+##### teoria.interval.from -> Interval.from
+##### teoria.interval.between -> Interval.between
+##### teoria.interval.invert -> Interval.invert
+##### teoria.interval.toCoord -> Interval.toCoord
+
+
+## Interval(coord)
+ - A representation of a music interval
+
+### Interval.toCoord(simpleInterval)
+ - Returns a `Interval` representing the interval expressed in string form.
+
+### Interval.from(from, to)
+ - Returns a note which is a given interval away from a root note.
+
+*from* - The `Note` which is the root of the measuring
+
+*to* - A `Interval`
+
+### Interval.between(from, to)
+ - Returns an interval object which represents the interval between two notes.
+
+*from* and *to* are two `Note`s which are the notes that the
+interval is measured from. For example if 'a' and 'c' are given, the resulting
+interval object would represent a minor third.
+
+```javascript
+Interval.between(teoria.note("a"), teoria.note("c'")) -> teoria.interval('m3')
+```
+
+### Interval.invert(simpleInterval)
+ - Returns the inversion of the interval provided
+
+*simpleInterval* - An interval represented in simple string form. Examples:
+
+ - 'm3' = minor third
+ - 'P4' = perfect fourth
+ - 'A4' = augmented fifth
+ - 'd7' = diminished seventh
+ - 'M6' = major sixth.
+
+`'m' = minor`, `'M' = major`, `'A' = augmented` and
+`'d' = diminished`
+
+The number may be prefixed with a `-` to signify that its direction is down. E.g.:
+
+`m-3` means a descending minor third, and `P-5` means a descending perfect fifth.
+
+#### Interval.coord
+ - The interval representation of the interval
+
+#### Interval.number()
+ - The interval number (A ninth = 9, A seventh = 7, fifteenth = 15)
+
+#### Interval.value()
+ - The value of the interval - That is a ninth = 9, but a downwards ninth is = -9
+
+#### Interval.toString()
+ - Returns the *simpleInterval* representation of the interval. E.g. `'P5'`,
+ `'M3'`, `'A9'`, etc.
+
+#### Interval.base()
+ - Returns the name of the simple interval (not compound)
+
+#### Interval.type()
+ - Returns the type of array, either `'perfect'` (1, 4, 5, 8) or `'minor'` (2, 3, 6, 7)
+
+#### Interval.quality([verbose])
+ - The quality of the interval (`'dd'`, `'d'` `'m'`, `'P'`, `'M'`, `'A'` or `'AA'`)
+
+*verbose* is set to a truish value, then long quality names are returned:
+ `'doubly diminished'`, `'diminished'`, `'minor'`, etc.
+
+#### Interval.direction([dir])
+ - The direction of the interval
+
+*dir* - If supplied, then the interval's direction is to the `newDirection`
+which is either `'up'` or `'down'`
+
+#### Interval#semitones()
+ - Returns the `number` of semitones the interval span.
+
+#### Interval#simple([ignoreDirection])
+ - Returns the simple part of the interval as a Interval. Example:
+
+*ignoreDirection* - An optional boolean that, if set to `true`, returns the
+"direction-agnostic" interval. That is the interval with a positive number.
+
+```javascript
+teoria.interval('M17').simple(); // #-> 'M3'
+teoria.interval('m23').simple(); // #-> 'm2'
+teoria.interval('P5').simple(); // #-> 'P5'
+teoria.interval('P-4').simple(); // #-> 'P-4'
+
+// With ignoreDirection = true
+teoria.interval('M3').simple(true); // #->'M3'
+teoria.interval('m-10').simple(true); // #-> 'm3'
+```
+
+*NB:* Note that above returned results are pseudo-results, as they will be
+returned wrapped in `Interval` objects.
+
+#### Interval#octaves()
+ - Returns the number of compound intervals
+
+#### Interval#isCompound()
+ - Returns a boolean value, showing if the interval is a compound interval
+
+#### Interval#add(interval)
+ - Adds the `interval` to this interval, and returns a `Interval`
+ representing the result of the addition
+
+#### Interval#equal(interval)
+ - Returns true if the supplied `interval` is equal to this interval
+
+#### Interval#greater(interval)
+ - Returns true if the supplied `interval` is greater than this interval
+
+#### Interval#smaller(interval)
+ - Returns true if the supplied `interval` is smaller than this interval
+
+#### Interval#invert()
+ - Returns the inverted interval as a `Interval`
+
+#### Interval#qualityValue() - *internal*
+ - Returns the relative to default, value of the quality.
+ E.g. a teoria.interval('M6'), will have a relative quality value of 1, as all the
+ intervals defaults to minor and perfect respectively.
+
ADDED public/teoria-master/index.js
Index: public/teoria-master/index.js
==================================================================
--- public/teoria-master/index.js
+++ public/teoria-master/index.js
@@ -0,0 +1,80 @@
+var Note = require('./lib/note');
+var Interval = require('./lib/interval');
+var Chord = require('./lib/chord');
+var Scale = require('./lib/scale');
+
+var teoria;
+
+// never thought I would write this, but: Legacy support
+function intervalConstructor(from, to) {
+ // Construct a Interval object from string representation
+ if (typeof from === 'string')
+ return Interval.toCoord(from);
+
+ if (typeof to === 'string' && from instanceof Note)
+ return Interval.from(from, Interval.toCoord(to));
+
+ if (to instanceof Interval && from instanceof Note)
+ return Interval.from(from, to);
+
+ if (to instanceof Note && from instanceof Note)
+ return Interval.between(from, to);
+
+ throw new Error('Invalid parameters');
+}
+
+intervalConstructor.toCoord = Interval.toCoord;
+intervalConstructor.from = Interval.from;
+intervalConstructor.between = Interval.between;
+intervalConstructor.invert = Interval.invert;
+
+function noteConstructor(name, duration) {
+ if (typeof name === 'string')
+ return Note.fromString(name, duration);
+ else
+ return new Note(name, duration);
+}
+
+noteConstructor.fromString = Note.fromString;
+noteConstructor.fromKey = Note.fromKey;
+noteConstructor.fromFrequency = Note.fromFrequency;
+noteConstructor.fromMIDI = Note.fromMIDI;
+
+function chordConstructor(name, symbol) {
+ if (typeof name === 'string') {
+ var root, octave;
+ root = name.match(/^([a-h])(x|#|bb|b?)/i);
+ if (root && root[0]) {
+ octave = typeof symbol === 'number' ? symbol.toString(10) : '4';
+ return new Chord(Note.fromString(root[0].toLowerCase() + octave),
+ name.substr(root[0].length));
+ }
+ } else if (name instanceof Note)
+ return new Chord(name, symbol);
+
+ throw new Error('Invalid Chord. Couldn\'t find note name');
+}
+
+function scaleConstructor(tonic, scale) {
+ tonic = (tonic instanceof Note) ? tonic : teoria.note(tonic);
+ return new Scale(tonic, scale);
+}
+
+teoria = {
+ note: noteConstructor,
+
+ chord: chordConstructor,
+
+ interval: intervalConstructor,
+
+ scale: scaleConstructor,
+
+ Note: Note,
+ Chord: Chord,
+ Scale: Scale,
+ Interval: Interval
+};
+
+
+require('./lib/sugar')(teoria);
+exports = module.exports = teoria;
ADDED public/teoria-master/lib/chord.js
Index: public/teoria-master/lib/chord.js
==================================================================
--- public/teoria-master/lib/chord.js
+++ public/teoria-master/lib/chord.js
@@ -0,0 +1,225 @@
+var daccord = require('daccord');
+var knowledge = require('./knowledge');
+var Note = require('./note');
+var Interval = require('./interval');
+
+function Chord(root, name) {
+ if (!(this instanceof Chord)) return new Chord(root, name);
+ name = name || '';
+ this.name = root.name().toUpperCase() + root.accidental() + name;
+ this.symbol = name;
+ this.root = root;
+ this.intervals = [];
+ this._voicing = [];
+
+ var bass = name.split('/');
+ if (bass.length === 2 && bass[1].trim() !== '9') {
+ name = bass[0];
+ bass = bass[1].trim();
+ } else {
+ bass = null;
+ }
+
+ this.intervals = daccord(name).map(Interval.toCoord);
+ this._voicing = this.intervals.slice();
+
+ if (bass) {
+ var intervals = this.intervals, bassInterval, note;
+ // Make sure the bass is atop of the root note
+ note = Note.fromString(bass + (root.octave() + 1)); // crude
+
+ bassInterval = Interval.between(root, note);
+ bass = bassInterval.simple();
+ bassInterval = bassInterval.invert().direction('down');
+
+ this._voicing = [bassInterval];
+ for (var i = 0, length = intervals.length; i < length; i++) {
+ if (!intervals[i].simple().equal(bass))
+ this._voicing.push(intervals[i]);
+ }
+ }
+}
+
+Chord.prototype = {
+ notes: function() {
+ var root = this.root;
+ return this.voicing().map(function(interval) {
+ return root.interval(interval);
+ });
+ },
+
+ simple: function() {
+ return this.notes().map(function(n) { return n.toString(true); });
+ },
+
+ bass: function() {
+ return this.root.interval(this._voicing[0]);
+ },
+
+ voicing: function(voicing) {
+ // Get the voicing
+ if (!voicing) {
+ return this._voicing;
+ }
+
+ // Set the voicing
+ this._voicing = [];
+ for (var i = 0, length = voicing.length; i < length; i++) {
+ this._voicing[i] = Interval.toCoord(voicing[i]);
+ }
+
+ return this;
+ },
+
+ resetVoicing: function() {
+ this._voicing = this.intervals;
+ },
+
+ dominant: function(additional) {
+ additional = additional || '';
+ return new Chord(this.root.interval('P5'), additional);
+ },
+
+ subdominant: function(additional) {
+ additional = additional || '';
+ return new Chord(this.root.interval('P4'), additional);
+ },
+
+ parallel: function(additional) {
+ additional = additional || '';
+ var quality = this.quality();
+
+ if (this.chordType() !== 'triad' || quality === 'diminished' ||
+ quality === 'augmented') {
+ throw new Error('Only major/minor triads have parallel chords');
+ }
+
+ if (quality === 'major') {
+ return new Chord(this.root.interval('m3', 'down'), 'm');
+ } else {
+ return new Chord(this.root.interval('m3', 'up'));
+ }
+ },
+
+ quality: function() {
+ var third, fifth, seventh, intervals = this.intervals;
+
+ for (var i = 0, length = intervals.length; i < length; i++) {
+ if (intervals[i].number() === 3) {
+ third = intervals[i];
+ } else if (intervals[i].number() === 5) {
+ fifth = intervals[i];
+ } else if (intervals[i].number() === 7) {
+ seventh = intervals[i];
+ }
+ }
+
+ if (!third) {
+ return;
+ }
+
+ third = (third.direction() === 'down') ? third.invert() : third;
+ third = third.simple().toString();
+
+ if (fifth) {
+ fifth = (fifth.direction === 'down') ? fifth.invert() : fifth;
+ fifth = fifth.simple().toString();
+ }
+
+ if (seventh) {
+ seventh = (seventh.direction === 'down') ? seventh.invert() : seventh;
+ seventh = seventh.simple().toString();
+ }
+
+ if (third === 'M3') {
+ if (fifth === 'A5') {
+ return 'augmented';
+ } else if (fifth === 'P5') {
+ return (seventh === 'm7') ? 'dominant' : 'major';
+ }
+
+ return 'major';
+ } else if (third === 'm3') {
+ if (fifth === 'P5') {
+ return 'minor';
+ } else if (fifth === 'd5') {
+ return (seventh === 'm7') ? 'half-diminished' : 'diminished';
+ }
+
+ return 'minor';
+ }
+ },
+
+ chordType: function() { // In need of better name
+ var length = this.intervals.length, interval, has, invert, i, name;
+
+ if (length === 2) {
+ return 'dyad';
+ } else if (length === 3) {
+ has = {unison: false, third: false, fifth: false};
+ for (i = 0; i < length; i++) {
+ interval = this.intervals[i];
+ invert = interval.invert();
+ if (interval.base() in has) {
+ has[interval.base()] = true;
+ } else if (invert.base() in has) {
+ has[invert.base()] = true;
+ }
+ }
+
+ name = (has.unison && has.third && has.fifth) ? 'triad' : 'trichord';
+ } else if (length === 4) {
+ has = {unison: false, third: false, fifth: false, seventh: false};
+ for (i = 0; i < length; i++) {
+ interval = this.intervals[i];
+ invert = interval.invert();
+ if (interval.base() in has) {
+ has[interval.base()] = true;
+ } else if (invert.base() in has) {
+ has[invert.base()] = true;
+ }
+ }
+
+ if (has.unison && has.third && has.fifth && has.seventh) {
+ name = 'tetrad';
+ }
+ }
+
+ return name || 'unknown';
+ },
+
+ get: function(interval) {
+ if (typeof interval === 'string' && interval in knowledge.stepNumber) {
+ var intervals = this.intervals, i, length;
+
+ interval = knowledge.stepNumber[interval];
+ for (i = 0, length = intervals.length; i < length; i++) {
+ if (intervals[i].number() === interval) {
+ return this.root.interval(intervals[i]);
+ }
+ }
+
+ return null;
+ } else {
+ throw new Error('Invalid interval name');
+ }
+ },
+
+ interval: function(interval) {
+ return new Chord(this.root.interval(interval), this.symbol);
+ },
+
+ transpose: function(interval) {
+ this.root.transpose(interval);
+ this.name = this.root.name().toUpperCase() +
+ this.root.accidental() + this.symbol;
+
+ return this;
+ },
+
+ toString: function() {
+ return this.name;
+ }
+};
+
+module.exports = Chord;
ADDED public/teoria-master/lib/interval.js
Index: public/teoria-master/lib/interval.js
==================================================================
--- public/teoria-master/lib/interval.js
+++ public/teoria-master/lib/interval.js
@@ -0,0 +1,171 @@
+var knowledge = require('./knowledge');
+var vector = require('./vector');
+var toCoord = require('interval-coords');
+
+function Interval(coord) {
+ if (!(this instanceof Interval)) return new Interval(coord);
+ this.coord = coord;
+}
+
+Interval.prototype = {
+ name: function() {
+ return knowledge.intervalsIndex[this.number() - 1];
+ },
+
+ semitones: function() {
+ return vector.sum(vector.mul(this.coord, [12, 7]));
+ },
+
+ number: function() {
+ return Math.abs(this.value());
+ },
+
+ value: function() {
+ var toMultiply = Math.floor((this.coord[1] - 2) / 7) + 1;
+ var product = vector.mul(knowledge.sharp, toMultiply);
+ var without = vector.sub(this.coord, product);
+ var i = knowledge.intervalFromFifth[without[1] + 5];
+ var diff = without[0] - knowledge.intervals[i][0];
+ var val = knowledge.stepNumber[i] + diff * 7;
+
+ return (val > 0) ? val : val - 2;
+ },
+
+ type: function() {
+ return knowledge.intervals[this.base()][0] <= 1 ? 'perfect' : 'minor';
+ },
+
+ base: function() {
+ var product = vector.mul(knowledge.sharp, this.qualityValue());
+ var fifth = vector.sub(this.coord, product)[1];
+ fifth = this.value() > 0 ? fifth + 5 : -(fifth - 5) % 7;
+ fifth = fifth < 0 ? knowledge.intervalFromFifth.length + fifth : fifth;
+
+ var name = knowledge.intervalFromFifth[fifth];
+ if (name === 'unison' && this.number() >= 8)
+ name = 'octave';
+
+ return name;
+ },
+
+ direction: function(dir) {
+ if (dir) {
+ var is = this.value() >= 1 ? 'up' : 'down';
+ if (is !== dir)
+ this.coord = vector.mul(this.coord, -1);
+
+ return this;
+ }
+ else
+ return this.value() >= 1 ? 'up' : 'down';
+ },
+
+ simple: function(ignore) {
+ // Get the (upwards) base interval (with quality)
+ var simple = knowledge.intervals[this.base()];
+ var toAdd = vector.mul(knowledge.sharp, this.qualityValue());
+ simple = vector.add(simple, toAdd);
+
+ // Turn it around if necessary
+ if (!ignore)
+ simple = this.direction() === 'down' ? vector.mul(simple, -1) : simple;
+
+ return new Interval(simple);
+ },
+
+ isCompound: function() {
+ return this.number() > 8;
+ },
+
+ octaves: function() {
+ var toSubtract, without, octaves;
+
+ if (this.direction() === 'up') {
+ toSubtract = vector.mul(knowledge.sharp, this.qualityValue());
+ without = vector.sub(this.coord, toSubtract);
+ octaves = without[0] - knowledge.intervals[this.base()][0];
+ } else {
+ toSubtract = vector.mul(knowledge.sharp, -this.qualityValue());
+ without = vector.sub(this.coord, toSubtract);
+ octaves = -(without[0] + knowledge.intervals[this.base()][0]);
+ }
+
+ return octaves;
+ },
+
+ invert: function() {
+ var i = this.base();
+ var qual = this.qualityValue();
+ var acc = this.type() === 'minor' ? -(qual - 1) : -qual;
+ var idx = 9 - knowledge.stepNumber[i] - 1;
+ var coord = knowledge.intervals[knowledge.intervalsIndex[idx]];
+ coord = vector.add(coord, vector.mul(knowledge.sharp, acc));
+
+ return new Interval(coord);
+ },
+
+ quality: function(lng) {
+ var quality = knowledge.alterations[this.type()][this.qualityValue() + 2];
+
+ return lng ? knowledge.qualityLong[quality] : quality;
+ },
+
+ qualityValue: function() {
+ if (this.direction() === 'down')
+ return Math.floor((-this.coord[1] - 2) / 7) + 1;
+ else
+ return Math.floor((this.coord[1] - 2) / 7) + 1;
+ },
+
+ equal: function(interval) {
+ return this.coord[0] === interval.coord[0] &&
+ this.coord[1] === interval.coord[1];
+ },
+
+ greater: function(interval) {
+ var semi = this.semitones();
+ var isemi = interval.semitones();
+
+ // If equal in absolute size, measure which interval is bigger
+ // For example P4 is bigger than A3
+ return (semi === isemi) ?
+ (this.number() > interval.number()) : (semi > isemi);
+ },
+
+ smaller: function(interval) {
+ return !this.equal(interval) && !this.greater(interval);
+ },
+
+ add: function(interval) {
+ return new Interval(vector.add(this.coord, interval.coord));
+ },
+
+ toString: function(ignore) {
+ // If given true, return the positive value
+ var number = ignore ? this.number() : this.value();
+
+ return this.quality() + number;
+ }
+};
+
+Interval.toCoord = function(simple) {
+ var coord = toCoord(simple);
+ if (!coord)
+ throw new Error('Invalid simple format interval');
+
+ return new Interval(coord);
+};
+
+Interval.from = function(from, to) {
+ return from.interval(to);
+};
+
+Interval.between = function(from, to) {
+ return new Interval(vector.sub(to.coord, from.coord));
+};
+
+Interval.invert = function(sInterval) {
+ return Interval.toCoord(sInterval).invert().toString();
+};
+
+module.exports = Interval;
ADDED public/teoria-master/lib/knowledge.js
Index: public/teoria-master/lib/knowledge.js
==================================================================
--- public/teoria-master/lib/knowledge.js
+++ public/teoria-master/lib/knowledge.js
@@ -0,0 +1,162 @@
+// Note coordinates [octave, fifth] relative to C
+module.exports = {
+ notes: {
+ c: [0, 0],
+ d: [-1, 2],
+ e: [-2, 4],
+ f: [1, -1],
+ g: [0, 1],
+ a: [-1, 3],
+ b: [-2, 5],
+ h: [-2, 5]
+ },
+
+ intervals: {
+ unison: [0, 0],
+ second: [3, -5],
+ third: [2, -3],
+ fourth: [1, -1],
+ fifth: [0, 1],
+ sixth: [3, -4],
+ seventh: [2, -2],
+ octave: [1, 0]
+ },
+
+ intervalFromFifth: ['second', 'sixth', 'third', 'seventh', 'fourth',
+ 'unison', 'fifth'],
+
+ intervalsIndex: ['unison', 'second', 'third', 'fourth', 'fifth',
+ 'sixth', 'seventh', 'octave', 'ninth', 'tenth',
+ 'eleventh', 'twelfth', 'thirteenth', 'fourteenth',
+ 'fifteenth'],
+
+// linear index to fifth = (2 * index + 1) % 7
+ fifths: ['f', 'c', 'g', 'd', 'a', 'e', 'b'],
+ accidentals: ['bb', 'b', '', '#', 'x'],
+
+ sharp: [-4, 7],
+ A4: [3, 3],
+
+ durations: {
+ '0.25': 'longa',
+ '0.5': 'breve',
+ '1': 'whole',
+ '2': 'half',
+ '4': 'quarter',
+ '8': 'eighth',
+ '16': 'sixteenth',
+ '32': 'thirty-second',
+ '64': 'sixty-fourth',
+ '128': 'hundred-twenty-eighth'
+ },
+
+ qualityLong: {
+ P: 'perfect',
+ M: 'major',
+ m: 'minor',
+ A: 'augmented',
+ AA: 'doubly augmented',
+ d: 'diminished',
+ dd: 'doubly diminished'
+ },
+
+ alterations: {
+ perfect: ['dd', 'd', 'P', 'A', 'AA'],
+ minor: ['dd', 'd', 'm', 'M', 'A', 'AA']
+ },
+
+ symbols: {
+ 'min': ['m3', 'P5'],
+ 'm': ['m3', 'P5'],
+ '-': ['m3', 'P5'],
+
+ 'M': ['M3', 'P5'],
+ '': ['M3', 'P5'],
+
+ '+': ['M3', 'A5'],
+ 'aug': ['M3', 'A5'],
+
+ 'dim': ['m3', 'd5'],
+ 'o': ['m3', 'd5'],
+
+ 'maj': ['M3', 'P5', 'M7'],
+ 'dom': ['M3', 'P5', 'm7'],
+ 'ø': ['m3', 'd5', 'm7'],
+
+ '5': ['P5']
+ },
+
+ chordShort: {
+ 'major': 'M',
+ 'minor': 'm',
+ 'augmented': 'aug',
+ 'diminished': 'dim',
+ 'half-diminished': '7b5',
+ 'power': '5',
+ 'dominant': '7'
+ },
+
+ stepNumber: {
+ 'unison': 1,
+ 'first': 1,
+ 'second': 2,
+ 'third': 3,
+ 'fourth': 4,
+ 'fifth': 5,
+ 'sixth': 6,
+ 'seventh': 7,
+ 'octave': 8,
+ 'ninth': 9,
+ 'eleventh': 11,
+ 'thirteenth': 13
+ },
+
+ // Adjusted Shearer syllables - Chromatic solfege system
+ // Some intervals are not provided for. These include:
+ // dd2 - Doubly diminished second
+ // dd3 - Doubly diminished third
+ // AA3 - Doubly augmented third
+ // dd6 - Doubly diminished sixth
+ // dd7 - Doubly diminished seventh
+ // AA7 - Doubly augmented seventh
+ intervalSolfege: {
+ 'dd1': 'daw',
+ 'd1': 'de',
+ 'P1': 'do',
+ 'A1': 'di',
+ 'AA1': 'dai',
+ 'd2': 'raw',
+ 'm2': 'ra',
+ 'M2': 're',
+ 'A2': 'ri',
+ 'AA2': 'rai',
+ 'd3': 'maw',
+ 'm3': 'me',
+ 'M3': 'mi',
+ 'A3': 'mai',
+ 'dd4': 'faw',
+ 'd4': 'fe',
+ 'P4': 'fa',
+ 'A4': 'fi',
+ 'AA4': 'fai',
+ 'dd5': 'saw',
+ 'd5': 'se',
+ 'P5': 'so',
+ 'A5': 'si',
+ 'AA5': 'sai',
+ 'd6': 'law',
+ 'm6': 'le',
+ 'M6': 'la',
+ 'A6': 'li',
+ 'AA6': 'lai',
+ 'd7': 'taw',
+ 'm7': 'te',
+ 'M7': 'ti',
+ 'A7': 'tai',
+ 'dd8': 'daw',
+ 'd8': 'de',
+ 'P8': 'do',
+ 'A8': 'di',
+ 'AA8': 'dai'
+ }
+};
ADDED public/teoria-master/lib/note.js
Index: public/teoria-master/lib/note.js
==================================================================
--- public/teoria-master/lib/note.js
+++ public/teoria-master/lib/note.js
@@ -0,0 +1,226 @@
+var scientific = require('scientific-notation');
+var helmholtz = require('helmholtz');
+var pitchFq = require('pitch-fq');
+var knowledge = require('./knowledge');
+var vector = require('./vector');
+var Interval = require('./interval');
+
+function pad(str, ch, len) {
+ for (; len > 0; len--) {
+ str += ch;
+ }
+
+ return str;
+}
+
+
+function Note(coord, duration) {
+ if (!(this instanceof Note)) return new Note(coord, duration);
+ duration = duration || {};
+
+ this.duration = { value: duration.value || 4, dots: duration.dots || 0 };
+ this.coord = coord;
+}
+
+Note.prototype = {
+ octave: function() {
+ return this.coord[0] + knowledge.A4[0] - knowledge.notes[this.name()][0] +
+ this.accidentalValue() * 4;
+ },
+
+ name: function() {
+ var value = this.accidentalValue();
+ var idx = this.coord[1] + knowledge.A4[1] - value * 7 + 1;
+ return knowledge.fifths[idx];
+ },
+
+ accidentalValue: function() {
+ return Math.round((this.coord[1] + knowledge.A4[1] - 2) / 7);
+ },
+
+ accidental: function() {
+ return knowledge.accidentals[this.accidentalValue() + 2];
+ },
+
+ /**
+ * Returns the key number of the note
+ */
+ key: function(white) {
+ if (white)
+ return this.coord[0] * 7 + this.coord[1] * 4 + 29;
+ else
+ return this.coord[0] * 12 + this.coord[1] * 7 + 49;
+ },
+
+ /**
+ * Returns a number ranging from 0-127 representing a MIDI note value
+ */
+ midi: function() {
+ return this.key() + 20;
+ },
+
+ /**
+ * Calculates and returns the frequency of the note.
+ * Optional concert pitch (def. 440)
+ */
+ fq: function(concertPitch) {
+ return pitchFq(this.coord, concertPitch);
+ },
+
+ /**
+ * Returns the pitch class index (chroma) of the note
+ */
+ chroma: function() {
+ var value = (vector.sum(vector.mul(this.coord, [12, 7])) - 3) % 12;
+
+ return (value < 0) ? value + 12 : value;
+ },
+
+ interval: function(interval) {
+ if (typeof interval === 'string') interval = Interval.toCoord(interval);
+
+ if (interval instanceof Interval)
+ return new Note(vector.add(this.coord, interval.coord), this.duration);
+ else if (interval instanceof Note)
+ return new Interval(vector.sub(interval.coord, this.coord));
+ },
+
+ transpose: function(interval) {
+ this.coord = vector.add(this.coord, interval.coord);
+ return this;
+ },
+
+ /**
+ * Returns the Helmholtz notation form of the note (fx C,, d' F# g#'')
+ */
+ helmholtz: function() {
+ var octave = this.octave();
+ var name = this.name();
+ name = octave < 3 ? name.toUpperCase() : name.toLowerCase();
+ var padchar = octave < 3 ? ',' : '\'';
+ var padcount = octave < 2 ? 2 - octave : octave - 3;
+
+ return pad(name + this.accidental(), padchar, padcount);
+ },
+
+ /**
+ * Returns the scientific notation form of the note (fx E4, Bb3, C#7 etc.)
+ */
+ scientific: function() {
+ return this.name().toUpperCase() + this.accidental() + this.octave();
+ },
+
+ /**
+ * Returns notes that are enharmonic with this note.
+ */
+ enharmonics: function(oneaccidental) {
+ var key = this.key(), limit = oneaccidental ? 2 : 3;
+
+ return ['m3', 'm2', 'm-2', 'm-3']
+ .map(this.interval.bind(this))
+ .filter(function(note) {
+ var acc = note.accidentalValue();
+ var diff = key - (note.key() - acc);
+
+ if (diff < limit && diff > -limit) {
+ var product = vector.mul(knowledge.sharp, diff - acc);
+ note.coord = vector.add(note.coord, product);
+ return true;
+ }
+ });
+ },
+
+ solfege: function(scale, showOctaves) {
+ var interval = scale.tonic.interval(this), solfege, stroke, count;
+ if (interval.direction() === 'down')
+ interval = interval.invert();
+
+ if (showOctaves) {
+ count = (this.key(true) - scale.tonic.key(true)) / 7;
+ count = (count >= 0) ? Math.floor(count) : -(Math.ceil(-count));
+ stroke = (count >= 0) ? '\'' : ',';
+ }
+
+ solfege = knowledge.intervalSolfege[interval.simple(true).toString()];
+ return (showOctaves) ? pad(solfege, stroke, Math.abs(count)) : solfege;
+ },
+
+ scaleDegree: function(scale) {
+ var inter = scale.tonic.interval(this);
+
+ // If the direction is down, or we're dealing with an octave - invert it
+ if (inter.direction() === 'down' ||
+ (inter.coord[1] === 0 && inter.coord[0] !== 0)) {
+ inter = inter.invert();
+ }
+
+ inter = inter.simple(true).coord;
+
+ return scale.scale.reduce(function(index, current, i) {
+ var coord = Interval.toCoord(current).coord;
+ return coord[0] === inter[0] && coord[1] === inter[1] ? i + 1 : index;
+ }, 0);
+ },
+
+ /**
+ * Returns the name of the duration value,
+ * such as 'whole', 'quarter', 'sixteenth' etc.
+ */
+ durationName: function() {
+ return knowledge.durations[this.duration.value];
+ },
+
+ /**
+ * Returns the duration of the note (including dots)
+ * in seconds. The first argument is the tempo in beats
+ * per minute, the second is the beat unit (i.e. the
+ * lower numeral in a time signature).
+ */
+ durationInSeconds: function(bpm, beatUnit) {
+ var secs = (60 / bpm) / (this.duration.value / 4) / (beatUnit / 4);
+ return secs * 2 - secs / Math.pow(2, this.duration.dots);
+ },
+
+ /**
+ * Returns the name of the note, with an optional display of octave number
+ */
+ toString: function(dont) {
+ return this.name() + this.accidental() + (dont ? '' : this.octave());
+ }
+};
+
+Note.fromString = function(name, dur) {
+ var coord = scientific(name);
+ if (!coord) coord = helmholtz(name);
+ return new Note(coord, dur);
+};
+
+Note.fromKey = function(key) {
+ var octave = Math.floor((key - 4) / 12);
+ var distance = key - (octave * 12) - 4;
+ var name = knowledge.fifths[(2 * Math.round(distance / 2) + 1) % 7];
+ var subDiff = vector.sub(knowledge.notes[name], knowledge.A4);
+ var note = vector.add(subDiff, [octave + 1, 0]);
+ var diff = (key - 49) - vector.sum(vector.mul(note, [12, 7]));
+
+ var arg = diff ? vector.add(note, vector.mul(knowledge.sharp, diff)) : note;
+ return new Note(arg);
+};
+
+Note.fromFrequency = function(fq, concertPitch) {
+ var key, cents, originalFq;
+ concertPitch = concertPitch || 440;
+
+ key = 49 + 12 * ((Math.log(fq) - Math.log(concertPitch)) / Math.log(2));
+ key = Math.round(key);
+ originalFq = concertPitch * Math.pow(2, (key - 49) / 12);
+ cents = 1200 * (Math.log(fq / originalFq) / Math.log(2));
+
+ return { note: Note.fromKey(key), cents: cents };
+};
+
+Note.fromMIDI = function(note) {
+ return Note.fromKey(note - 20);
+};
+
+module.exports = Note;
ADDED public/teoria-master/lib/scale.js
Index: public/teoria-master/lib/scale.js
==================================================================
--- public/teoria-master/lib/scale.js
+++ public/teoria-master/lib/scale.js
@@ -0,0 +1,125 @@
+var knowledge = require('./knowledge');
+var Interval = require('./interval');
+
+var scales = {
+ aeolian: ['P1', 'M2', 'm3', 'P4', 'P5', 'm6', 'm7'],
+ blues: ['P1', 'm3', 'P4', 'd5', 'P5', 'm7'],
+ chromatic: ['P1', 'm2', 'M2', 'm3', 'M3', 'P4',
+ 'A4', 'P5', 'm6', 'M6', 'm7', 'M7'],
+ dorian: ['P1', 'M2', 'm3', 'P4', 'P5', 'M6', 'm7'],
+ doubleharmonic: ['P1', 'm2', 'M3', 'P4', 'P5', 'm6', 'M7'],
+ harmonicminor: ['P1', 'M2', 'm3', 'P4', 'P5', 'm6', 'M7'],
+ ionian: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'M7'],
+ locrian: ['P1', 'm2', 'm3', 'P4', 'd5', 'm6', 'm7'],
+ lydian: ['P1', 'M2', 'M3', 'A4', 'P5', 'M6', 'M7'],
+ majorpentatonic: ['P1', 'M2', 'M3', 'P5', 'M6'],
+ melodicminor: ['P1', 'M2', 'm3', 'P4', 'P5', 'M6', 'M7'],
+ minorpentatonic: ['P1', 'm3', 'P4', 'P5', 'm7'],
+ mixolydian: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'm7'],
+ phrygian: ['P1', 'm2', 'm3', 'P4', 'P5', 'm6', 'm7'],
+ wholetone: ['P1', 'M2', 'M3', 'A4', 'A5', 'A6']
+};
+
+// synonyms
+scales.harmonicchromatic = scales.chromatic;
+scales.minor = scales.aeolian;
+scales.major = scales.ionian;
+scales.flamenco = scales.doubleharmonic;
+
+function Scale(tonic, scale) {
+ if (!(this instanceof Scale)) return new Scale(tonic, scale);
+ var scaleName, i;
+ if (!('coord' in tonic)) {
+ throw new Error('Invalid Tonic');
+ }
+
+ if (typeof scale === 'string') {
+ scaleName = scale;
+ scale = scales[scale];
+ if (!scale)
+ throw new Error('Invalid Scale');
+ } else {
+ for (i in scales) {
+ if (scales.hasOwnProperty(i)) {
+ if (scales[i].toString() === scale.toString()) {
+ scaleName = i;
+ break;
+ }
+ }
+ }
+ }
+
+ this.name = scaleName;
+ this.tonic = tonic;
+ this.scale = scale;
+}
+
+Scale.prototype = {
+ notes: function() {
+ var notes = [];
+
+ for (var i = 0, length = this.scale.length; i < length; i++) {
+ notes.push(this.tonic.interval(this.scale[i]));
+ }
+
+ return notes;
+ },
+
+ simple: function() {
+ return this.notes().map(function(n) { return n.toString(true); });
+ },
+
+ type: function() {
+ var length = this.scale.length - 2;
+ if (length < 8) {
+ return ['di', 'tri', 'tetra', 'penta', 'hexa', 'hepta', 'octa'][length] +
+ 'tonic';
+ }
+ },
+
+ get: function(i) {
+ var isStepStr = typeof i === 'string' && i in knowledge.stepNumber;
+ i = isStepStr ? knowledge.stepNumber[i] : i;
+ var len = this.scale.length;
+ var interval, octaves;
+
+ if (i < 0) {
+ interval = this.scale[i % len + len - 1];
+ octaves = Math.floor((i - 1) / len);
+ } else if (i % len === 0) {
+ interval = this.scale[len - 1];
+ octaves = (i / len) - 1;
+ } else {
+ interval = this.scale[i % len - 1];
+ octaves = Math.floor(i / len);
+ }
+
+ return this.tonic.interval(interval).interval(new Interval([octaves, 0]));
+ },
+
+ solfege: function(index, showOctaves) {
+ if (index)
+ return this.get(index).solfege(this, showOctaves);
+
+ return this.notes().map(function(n) {
+ return n.solfege(this, showOctaves);
+ });
+ },
+
+ interval: function(interval) {
+ interval = (typeof interval === 'string') ?
+ Interval.toCoord(interval) : interval;
+ return new Scale(this.tonic.interval(interval), this.scale);
+ },
+
+ transpose: function(interval) {
+ var scale = this.interval(interval);
+ this.scale = scale.scale;
+ this.tonic = scale.tonic;
+
+ return this;
+ }
+};
+Scale.KNOWN_SCALES = Object.keys(scales);
+
+module.exports = Scale;
ADDED public/teoria-master/lib/sugar.js
Index: public/teoria-master/lib/sugar.js
==================================================================
--- public/teoria-master/lib/sugar.js
+++ public/teoria-master/lib/sugar.js
@@ -0,0 +1,18 @@
+var knowledge = require('./knowledge');
+
+module.exports = function(teoria) {
+ var Note = teoria.Note;
+ var Chord = teoria.Chord;
+ var Scale = teoria.Scale;
+
+ Note.prototype.chord = function(chord) {
+ var isShortChord = chord in knowledge.chordShort;
+ chord = isShortChord ? knowledge.chordShort[chord] : chord;
+
+ return new Chord(this, chord);
+ };
+
+ Note.prototype.scale = function(scale) {
+ return new Scale(this, scale);
+ };
+};
ADDED public/teoria-master/lib/vector.js
Index: public/teoria-master/lib/vector.js
==================================================================
--- public/teoria-master/lib/vector.js
+++ public/teoria-master/lib/vector.js
@@ -0,0 +1,20 @@
+module.exports = {
+ add: function(note, interval) {
+ return [note[0] + interval[0], note[1] + interval[1]];
+ },
+
+ sub: function(note, interval) {
+ return [note[0] - interval[0], note[1] - interval[1]];
+ },
+
+ mul: function(note, interval) {
+ if (typeof interval === 'number')
+ return [note[0] * interval, note[1] * interval];
+ else
+ return [note[0] * interval[0], note[1] * interval[1]];
+ },
+
+ sum: function(coord) {
+ return coord[0] + coord[1];
+ }
+};
ADDED public/teoria-master/package.json
Index: public/teoria-master/package.json
==================================================================
--- public/teoria-master/package.json
+++ public/teoria-master/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "teoria",
+ "version": "2.5.0",
+ "description": "Music theory for JavaScript",
+ "homepage": "http://saebekassebil.github.com/teoria",
+ "keywords": [
+ "music",
+ "theory",
+ "jazz",
+ "classical",
+ "chord"
+ ],
+ "main": "./index.js",
+ "author": {
+ "name": "Jakob Miland ",
+ "email": "saebekassebil@gmail.com",
+ "url": "https://github.com/saebekassebil"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/saebekassebil/teoria"
+ },
+ "license": "MIT",
+ "scripts": {
+ "test": "vows --dot-matric test/*",
+ "lint": "jshint index.js lib/",
+ "bundle": "browserify index.js --standalone teoria > teoria.js",
+ "build": "npm run lint && npm test && npm run bundle"
+ },
+ "devDependencies": {
+ "jshint": ">=0.9.0",
+ "vows": ">= 0.6.0"
+ },
+ "dependencies": {
+ "daccord": "^1.0.1",
+ "helmholtz": "^2.0.1",
+ "interval-coords": "^1.1.1",
+ "pitch-fq": "^1.0.0",
+ "scientific-notation": "^1.0.2"
+ }
+}
ADDED public/teoria-master/teoria.js
Index: public/teoria-master/teoria.js
==================================================================
--- public/teoria-master/teoria.js
+++ public/teoria-master/teoria.js
@@ -0,0 +1,1404 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.teoria = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) ? val : val - 2;
+ },
+
+ type: function() {
+ return knowledge.intervals[this.base()][0] <= 1 ? 'perfect' : 'minor';
+ },
+
+ base: function() {
+ var product = vector.mul(knowledge.sharp, this.qualityValue());
+ var fifth = vector.sub(this.coord, product)[1];
+ fifth = this.value() > 0 ? fifth + 5 : -(fifth - 5) % 7;
+ fifth = fifth < 0 ? knowledge.intervalFromFifth.length + fifth : fifth;
+
+ var name = knowledge.intervalFromFifth[fifth];
+ if (name === 'unison' && this.number() >= 8)
+ name = 'octave';
+
+ return name;
+ },
+
+ direction: function(dir) {
+ if (dir) {
+ var is = this.value() >= 1 ? 'up' : 'down';
+ if (is !== dir)
+ this.coord = vector.mul(this.coord, -1);
+
+ return this;
+ }
+ else
+ return this.value() >= 1 ? 'up' : 'down';
+ },
+
+ simple: function(ignore) {
+ // Get the (upwards) base interval (with quality)
+ var simple = knowledge.intervals[this.base()];
+ var toAdd = vector.mul(knowledge.sharp, this.qualityValue());
+ simple = vector.add(simple, toAdd);
+
+ // Turn it around if necessary
+ if (!ignore)
+ simple = this.direction() === 'down' ? vector.mul(simple, -1) : simple;
+
+ return new Interval(simple);
+ },
+
+ isCompound: function() {
+ return this.number() > 8;
+ },
+
+ octaves: function() {
+ var toSubtract, without, octaves;
+
+ if (this.direction() === 'up') {
+ toSubtract = vector.mul(knowledge.sharp, this.qualityValue());
+ without = vector.sub(this.coord, toSubtract);
+ octaves = without[0] - knowledge.intervals[this.base()][0];
+ } else {
+ toSubtract = vector.mul(knowledge.sharp, -this.qualityValue());
+ without = vector.sub(this.coord, toSubtract);
+ octaves = -(without[0] + knowledge.intervals[this.base()][0]);
+ }
+
+ return octaves;
+ },
+
+ invert: function() {
+ var i = this.base();
+ var qual = this.qualityValue();
+ var acc = this.type() === 'minor' ? -(qual - 1) : -qual;
+ var idx = 9 - knowledge.stepNumber[i] - 1;
+ var coord = knowledge.intervals[knowledge.intervalsIndex[idx]];
+ coord = vector.add(coord, vector.mul(knowledge.sharp, acc));
+
+ return new Interval(coord);
+ },
+
+ quality: function(lng) {
+ var quality = knowledge.alterations[this.type()][this.qualityValue() + 2];
+
+ return lng ? knowledge.qualityLong[quality] : quality;
+ },
+
+ qualityValue: function() {
+ if (this.direction() === 'down')
+ return Math.floor((-this.coord[1] - 2) / 7) + 1;
+ else
+ return Math.floor((this.coord[1] - 2) / 7) + 1;
+ },
+
+ equal: function(interval) {
+ return this.coord[0] === interval.coord[0] &&
+ this.coord[1] === interval.coord[1];
+ },
+
+ greater: function(interval) {
+ var semi = this.semitones();
+ var isemi = interval.semitones();
+
+ // If equal in absolute size, measure which interval is bigger
+ // For example P4 is bigger than A3
+ return (semi === isemi) ?
+ (this.number() > interval.number()) : (semi > isemi);
+ },
+
+ smaller: function(interval) {
+ return !this.equal(interval) && !this.greater(interval);
+ },
+
+ add: function(interval) {
+ return new Interval(vector.add(this.coord, interval.coord));
+ },
+
+ toString: function(ignore) {
+ // If given true, return the positive value
+ var number = ignore ? this.number() : this.value();
+
+ return this.quality() + number;
+ }
+};
+
+Interval.toCoord = function(simple) {
+ var coord = toCoord(simple);
+ if (!coord)
+ throw new Error('Invalid simple format interval');
+
+ return new Interval(coord);
+};
+
+Interval.from = function(from, to) {
+ return from.interval(to);
+};
+
+Interval.between = function(from, to) {
+ return new Interval(vector.sub(to.coord, from.coord));
+};
+
+Interval.invert = function(sInterval) {
+ return Interval.toCoord(sInterval).invert().toString();
+};
+
+module.exports = Interval;
+
+},{"./knowledge":4,"./vector":8,"interval-coords":12}],4:[function(require,module,exports){
+// Note coordinates [octave, fifth] relative to C
+module.exports = {
+ notes: {
+ c: [0, 0],
+ d: [-1, 2],
+ e: [-2, 4],
+ f: [1, -1],
+ g: [0, 1],
+ a: [-1, 3],
+ b: [-2, 5],
+ h: [-2, 5]
+ },
+
+ intervals: {
+ unison: [0, 0],
+ second: [3, -5],
+ third: [2, -3],
+ fourth: [1, -1],
+ fifth: [0, 1],
+ sixth: [3, -4],
+ seventh: [2, -2],
+ octave: [1, 0]
+ },
+
+ intervalFromFifth: ['second', 'sixth', 'third', 'seventh', 'fourth',
+ 'unison', 'fifth'],
+
+ intervalsIndex: ['unison', 'second', 'third', 'fourth', 'fifth',
+ 'sixth', 'seventh', 'octave', 'ninth', 'tenth',
+ 'eleventh', 'twelfth', 'thirteenth', 'fourteenth',
+ 'fifteenth'],
+
+// linear index to fifth = (2 * index + 1) % 7
+ fifths: ['f', 'c', 'g', 'd', 'a', 'e', 'b'],
+ accidentals: ['bb', 'b', '', '#', 'x'],
+
+ sharp: [-4, 7],
+ A4: [3, 3],
+
+ durations: {
+ '0.25': 'longa',
+ '0.5': 'breve',
+ '1': 'whole',
+ '2': 'half',
+ '4': 'quarter',
+ '8': 'eighth',
+ '16': 'sixteenth',
+ '32': 'thirty-second',
+ '64': 'sixty-fourth',
+ '128': 'hundred-twenty-eighth'
+ },
+
+ qualityLong: {
+ P: 'perfect',
+ M: 'major',
+ m: 'minor',
+ A: 'augmented',
+ AA: 'doubly augmented',
+ d: 'diminished',
+ dd: 'doubly diminished'
+ },
+
+ alterations: {
+ perfect: ['dd', 'd', 'P', 'A', 'AA'],
+ minor: ['dd', 'd', 'm', 'M', 'A', 'AA']
+ },
+
+ symbols: {
+ 'min': ['m3', 'P5'],
+ 'm': ['m3', 'P5'],
+ '-': ['m3', 'P5'],
+
+ 'M': ['M3', 'P5'],
+ '': ['M3', 'P5'],
+
+ '+': ['M3', 'A5'],
+ 'aug': ['M3', 'A5'],
+
+ 'dim': ['m3', 'd5'],
+ 'o': ['m3', 'd5'],
+
+ 'maj': ['M3', 'P5', 'M7'],
+ 'dom': ['M3', 'P5', 'm7'],
+ 'ø': ['m3', 'd5', 'm7'],
+
+ '5': ['P5']
+ },
+
+ chordShort: {
+ 'major': 'M',
+ 'minor': 'm',
+ 'augmented': 'aug',
+ 'diminished': 'dim',
+ 'half-diminished': '7b5',
+ 'power': '5',
+ 'dominant': '7'
+ },
+
+ stepNumber: {
+ 'unison': 1,
+ 'first': 1,
+ 'second': 2,
+ 'third': 3,
+ 'fourth': 4,
+ 'fifth': 5,
+ 'sixth': 6,
+ 'seventh': 7,
+ 'octave': 8,
+ 'ninth': 9,
+ 'eleventh': 11,
+ 'thirteenth': 13
+ },
+
+ // Adjusted Shearer syllables - Chromatic solfege system
+ // Some intervals are not provided for. These include:
+ // dd2 - Doubly diminished second
+ // dd3 - Doubly diminished third
+ // AA3 - Doubly augmented third
+ // dd6 - Doubly diminished sixth
+ // dd7 - Doubly diminished seventh
+ // AA7 - Doubly augmented seventh
+ intervalSolfege: {
+ 'dd1': 'daw',
+ 'd1': 'de',
+ 'P1': 'do',
+ 'A1': 'di',
+ 'AA1': 'dai',
+ 'd2': 'raw',
+ 'm2': 'ra',
+ 'M2': 're',
+ 'A2': 'ri',
+ 'AA2': 'rai',
+ 'd3': 'maw',
+ 'm3': 'me',
+ 'M3': 'mi',
+ 'A3': 'mai',
+ 'dd4': 'faw',
+ 'd4': 'fe',
+ 'P4': 'fa',
+ 'A4': 'fi',
+ 'AA4': 'fai',
+ 'dd5': 'saw',
+ 'd5': 'se',
+ 'P5': 'so',
+ 'A5': 'si',
+ 'AA5': 'sai',
+ 'd6': 'law',
+ 'm6': 'le',
+ 'M6': 'la',
+ 'A6': 'li',
+ 'AA6': 'lai',
+ 'd7': 'taw',
+ 'm7': 'te',
+ 'M7': 'ti',
+ 'A7': 'tai',
+ 'dd8': 'daw',
+ 'd8': 'de',
+ 'P8': 'do',
+ 'A8': 'di',
+ 'AA8': 'dai'
+ }
+};
+
+},{}],5:[function(require,module,exports){
+var scientific = require('scientific-notation');
+var helmholtz = require('helmholtz');
+var pitchFq = require('pitch-fq');
+var knowledge = require('./knowledge');
+var vector = require('./vector');
+var Interval = require('./interval');
+
+function pad(str, ch, len) {
+ for (; len > 0; len--) {
+ str += ch;
+ }
+
+ return str;
+}
+
+
+function Note(coord, duration) {
+ if (!(this instanceof Note)) return new Note(coord, duration);
+ duration = duration || {};
+
+ this.duration = { value: duration.value || 4, dots: duration.dots || 0 };
+ this.coord = coord;
+}
+
+Note.prototype = {
+ octave: function() {
+ return this.coord[0] + knowledge.A4[0] - knowledge.notes[this.name()][0] +
+ this.accidentalValue() * 4;
+ },
+
+ name: function() {
+ var value = this.accidentalValue();
+ var idx = this.coord[1] + knowledge.A4[1] - value * 7 + 1;
+ return knowledge.fifths[idx];
+ },
+
+ accidentalValue: function() {
+ return Math.round((this.coord[1] + knowledge.A4[1] - 2) / 7);
+ },
+
+ accidental: function() {
+ return knowledge.accidentals[this.accidentalValue() + 2];
+ },
+
+ /**
+ * Returns the key number of the note
+ */
+ key: function(white) {
+ if (white)
+ return this.coord[0] * 7 + this.coord[1] * 4 + 29;
+ else
+ return this.coord[0] * 12 + this.coord[1] * 7 + 49;
+ },
+
+ /**
+ * Returns a number ranging from 0-127 representing a MIDI note value
+ */
+ midi: function() {
+ return this.key() + 20;
+ },
+
+ /**
+ * Calculates and returns the frequency of the note.
+ * Optional concert pitch (def. 440)
+ */
+ fq: function(concertPitch) {
+ return pitchFq(this.coord, concertPitch);
+ },
+
+ /**
+ * Returns the pitch class index (chroma) of the note
+ */
+ chroma: function() {
+ var value = (vector.sum(vector.mul(this.coord, [12, 7])) - 3) % 12;
+
+ return (value < 0) ? value + 12 : value;
+ },
+
+ interval: function(interval) {
+ if (typeof interval === 'string') interval = Interval.toCoord(interval);
+
+ if (interval instanceof Interval)
+ return new Note(vector.add(this.coord, interval.coord), this.duration);
+ else if (interval instanceof Note)
+ return new Interval(vector.sub(interval.coord, this.coord));
+ },
+
+ transpose: function(interval) {
+ this.coord = vector.add(this.coord, interval.coord);
+ return this;
+ },
+
+ /**
+ * Returns the Helmholtz notation form of the note (fx C,, d' F# g#'')
+ */
+ helmholtz: function() {
+ var octave = this.octave();
+ var name = this.name();
+ name = octave < 3 ? name.toUpperCase() : name.toLowerCase();
+ var padchar = octave < 3 ? ',' : '\'';
+ var padcount = octave < 2 ? 2 - octave : octave - 3;
+
+ return pad(name + this.accidental(), padchar, padcount);
+ },
+
+ /**
+ * Returns the scientific notation form of the note (fx E4, Bb3, C#7 etc.)
+ */
+ scientific: function() {
+ return this.name().toUpperCase() + this.accidental() + this.octave();
+ },
+
+ /**
+ * Returns notes that are enharmonic with this note.
+ */
+ enharmonics: function(oneaccidental) {
+ var key = this.key(), limit = oneaccidental ? 2 : 3;
+
+ return ['m3', 'm2', 'm-2', 'm-3']
+ .map(this.interval.bind(this))
+ .filter(function(note) {
+ var acc = note.accidentalValue();
+ var diff = key - (note.key() - acc);
+
+ if (diff < limit && diff > -limit) {
+ var product = vector.mul(knowledge.sharp, diff - acc);
+ note.coord = vector.add(note.coord, product);
+ return true;
+ }
+ });
+ },
+
+ solfege: function(scale, showOctaves) {
+ var interval = scale.tonic.interval(this), solfege, stroke, count;
+ if (interval.direction() === 'down')
+ interval = interval.invert();
+
+ if (showOctaves) {
+ count = (this.key(true) - scale.tonic.key(true)) / 7;
+ count = (count >= 0) ? Math.floor(count) : -(Math.ceil(-count));
+ stroke = (count >= 0) ? '\'' : ',';
+ }
+
+ solfege = knowledge.intervalSolfege[interval.simple(true).toString()];
+ return (showOctaves) ? pad(solfege, stroke, Math.abs(count)) : solfege;
+ },
+
+ scaleDegree: function(scale) {
+ var inter = scale.tonic.interval(this);
+
+ // If the direction is down, or we're dealing with an octave - invert it
+ if (inter.direction() === 'down' ||
+ (inter.coord[1] === 0 && inter.coord[0] !== 0)) {
+ inter = inter.invert();
+ }
+
+ inter = inter.simple(true).coord;
+
+ return scale.scale.reduce(function(index, current, i) {
+ var coord = Interval.toCoord(current).coord;
+ return coord[0] === inter[0] && coord[1] === inter[1] ? i + 1 : index;
+ }, 0);
+ },
+
+ /**
+ * Returns the name of the duration value,
+ * such as 'whole', 'quarter', 'sixteenth' etc.
+ */
+ durationName: function() {
+ return knowledge.durations[this.duration.value];
+ },
+
+ /**
+ * Returns the duration of the note (including dots)
+ * in seconds. The first argument is the tempo in beats
+ * per minute, the second is the beat unit (i.e. the
+ * lower numeral in a time signature).
+ */
+ durationInSeconds: function(bpm, beatUnit) {
+ var secs = (60 / bpm) / (this.duration.value / 4) / (beatUnit / 4);
+ return secs * 2 - secs / Math.pow(2, this.duration.dots);
+ },
+
+ /**
+ * Returns the name of the note, with an optional display of octave number
+ */
+ toString: function(dont) {
+ return this.name() + this.accidental() + (dont ? '' : this.octave());
+ }
+};
+
+Note.fromString = function(name, dur) {
+ var coord = scientific(name);
+ if (!coord) coord = helmholtz(name);
+ return new Note(coord, dur);
+};
+
+Note.fromKey = function(key) {
+ var octave = Math.floor((key - 4) / 12);
+ var distance = key - (octave * 12) - 4;
+ var name = knowledge.fifths[(2 * Math.round(distance / 2) + 1) % 7];
+ var subDiff = vector.sub(knowledge.notes[name], knowledge.A4);
+ var note = vector.add(subDiff, [octave + 1, 0]);
+ var diff = (key - 49) - vector.sum(vector.mul(note, [12, 7]));
+
+ var arg = diff ? vector.add(note, vector.mul(knowledge.sharp, diff)) : note;
+ return new Note(arg);
+};
+
+Note.fromFrequency = function(fq, concertPitch) {
+ var key, cents, originalFq;
+ concertPitch = concertPitch || 440;
+
+ key = 49 + 12 * ((Math.log(fq) - Math.log(concertPitch)) / Math.log(2));
+ key = Math.round(key);
+ originalFq = concertPitch * Math.pow(2, (key - 49) / 12);
+ cents = 1200 * (Math.log(fq / originalFq) / Math.log(2));
+
+ return { note: Note.fromKey(key), cents: cents };
+};
+
+Note.fromMIDI = function(note) {
+ return Note.fromKey(note - 20);
+};
+
+module.exports = Note;
+
+},{"./interval":3,"./knowledge":4,"./vector":8,"helmholtz":11,"pitch-fq":14,"scientific-notation":15}],6:[function(require,module,exports){
+var knowledge = require('./knowledge');
+var Interval = require('./interval');
+
+var scales = {
+ aeolian: ['P1', 'M2', 'm3', 'P4', 'P5', 'm6', 'm7'],
+ blues: ['P1', 'm3', 'P4', 'd5', 'P5', 'm7'],
+ chromatic: ['P1', 'm2', 'M2', 'm3', 'M3', 'P4',
+ 'A4', 'P5', 'm6', 'M6', 'm7', 'M7'],
+ dorian: ['P1', 'M2', 'm3', 'P4', 'P5', 'M6', 'm7'],
+ doubleharmonic: ['P1', 'm2', 'M3', 'P4', 'P5', 'm6', 'M7'],
+ harmonicminor: ['P1', 'M2', 'm3', 'P4', 'P5', 'm6', 'M7'],
+ ionian: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'M7'],
+ locrian: ['P1', 'm2', 'm3', 'P4', 'd5', 'm6', 'm7'],
+ lydian: ['P1', 'M2', 'M3', 'A4', 'P5', 'M6', 'M7'],
+ majorpentatonic: ['P1', 'M2', 'M3', 'P5', 'M6'],
+ melodicminor: ['P1', 'M2', 'm3', 'P4', 'P5', 'M6', 'M7'],
+ minorpentatonic: ['P1', 'm3', 'P4', 'P5', 'm7'],
+ mixolydian: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'm7'],
+ phrygian: ['P1', 'm2', 'm3', 'P4', 'P5', 'm6', 'm7'],
+ wholetone: ['P1', 'M2', 'M3', 'A4', 'A5', 'A6']
+};
+
+// synonyms
+scales.harmonicchromatic = scales.chromatic;
+scales.minor = scales.aeolian;
+scales.major = scales.ionian;
+scales.flamenco = scales.doubleharmonic;
+
+function Scale(tonic, scale) {
+ if (!(this instanceof Scale)) return new Scale(tonic, scale);
+ var scaleName, i;
+ if (!('coord' in tonic)) {
+ throw new Error('Invalid Tonic');
+ }
+
+ if (typeof scale === 'string') {
+ scaleName = scale;
+ scale = scales[scale];
+ if (!scale)
+ throw new Error('Invalid Scale');
+ } else {
+ for (i in scales) {
+ if (scales.hasOwnProperty(i)) {
+ if (scales[i].toString() === scale.toString()) {
+ scaleName = i;
+ break;
+ }
+ }
+ }
+ }
+
+ this.name = scaleName;
+ this.tonic = tonic;
+ this.scale = scale;
+}
+
+Scale.prototype = {
+ notes: function() {
+ var notes = [];
+
+ for (var i = 0, length = this.scale.length; i < length; i++) {
+ notes.push(this.tonic.interval(this.scale[i]));
+ }
+
+ return notes;
+ },
+
+ simple: function() {
+ return this.notes().map(function(n) { return n.toString(true); });
+ },
+
+ type: function() {
+ var length = this.scale.length - 2;
+ if (length < 8) {
+ return ['di', 'tri', 'tetra', 'penta', 'hexa', 'hepta', 'octa'][length] +
+ 'tonic';
+ }
+ },
+
+ get: function(i) {
+ var isStepStr = typeof i === 'string' && i in knowledge.stepNumber;
+ i = isStepStr ? knowledge.stepNumber[i] : i;
+ var len = this.scale.length;
+ var interval, octaves;
+
+ if (i < 0) {
+ interval = this.scale[i % len + len - 1];
+ octaves = Math.floor((i - 1) / len);
+ } else if (i % len === 0) {
+ interval = this.scale[len - 1];
+ octaves = (i / len) - 1;
+ } else {
+ interval = this.scale[i % len - 1];
+ octaves = Math.floor(i / len);
+ }
+
+ return this.tonic.interval(interval).interval(new Interval([octaves, 0]));
+ },
+
+ solfege: function(index, showOctaves) {
+ if (index)
+ return this.get(index).solfege(this, showOctaves);
+
+ return this.notes().map(function(n) {
+ return n.solfege(this, showOctaves);
+ });
+ },
+
+ interval: function(interval) {
+ interval = (typeof interval === 'string') ?
+ Interval.toCoord(interval) : interval;
+ return new Scale(this.tonic.interval(interval), this.scale);
+ },
+
+ transpose: function(interval) {
+ var scale = this.interval(interval);
+ this.scale = scale.scale;
+ this.tonic = scale.tonic;
+
+ return this;
+ }
+};
+Scale.KNOWN_SCALES = Object.keys(scales);
+
+module.exports = Scale;
+
+},{"./interval":3,"./knowledge":4}],7:[function(require,module,exports){
+var knowledge = require('./knowledge');
+
+module.exports = function(teoria) {
+ var Note = teoria.Note;
+ var Chord = teoria.Chord;
+ var Scale = teoria.Scale;
+
+ Note.prototype.chord = function(chord) {
+ var isShortChord = chord in knowledge.chordShort;
+ chord = isShortChord ? knowledge.chordShort[chord] : chord;
+
+ return new Chord(this, chord);
+ };
+
+ Note.prototype.scale = function(scale) {
+ return new Scale(this, scale);
+ };
+};
+
+},{"./knowledge":4}],8:[function(require,module,exports){
+module.exports = {
+ add: function(note, interval) {
+ return [note[0] + interval[0], note[1] + interval[1]];
+ },
+
+ sub: function(note, interval) {
+ return [note[0] - interval[0], note[1] - interval[1]];
+ },
+
+ mul: function(note, interval) {
+ if (typeof interval === 'number')
+ return [note[0] * interval, note[1] * interval];
+ else
+ return [note[0] * interval[0], note[1] * interval[1]];
+ },
+
+ sum: function(coord) {
+ return coord[0] + coord[1];
+ }
+};
+
+},{}],9:[function(require,module,exports){
+var accidentalValues = {
+ 'bb': -2,
+ 'b': -1,
+ '': 0,
+ '#': 1,
+ 'x': 2
+};
+
+module.exports = function accidentalNumber(acc) {
+ return accidentalValues[acc];
+}
+
+module.exports.interval = function accidentalInterval(acc) {
+ var val = accidentalValues[acc];
+ return [-4 * val, 7 * val];
+}
+
+},{}],10:[function(require,module,exports){
+var SYMBOLS = {
+ 'm': ['m3', 'P5'],
+ 'mi': ['m3', 'P5'],
+ 'min': ['m3', 'P5'],
+ '-': ['m3', 'P5'],
+
+ 'M': ['M3', 'P5'],
+ 'ma': ['M3', 'P5'],
+ '': ['M3', 'P5'],
+
+ '+': ['M3', 'A5'],
+ 'aug': ['M3', 'A5'],
+
+ 'dim': ['m3', 'd5'],
+ 'o': ['m3', 'd5'],
+
+ 'maj': ['M3', 'P5', 'M7'],
+ 'dom': ['M3', 'P5', 'm7'],
+ 'ø': ['m3', 'd5', 'm7'],
+
+ '5': ['P5'],
+
+ '6/9': ['M3', 'P5', 'M6', 'M9']
+};
+
+module.exports = function(symbol) {
+ var c, parsing = 'quality', additionals = [], name, chordLength = 2
+ var notes = ['P1', 'M3', 'P5', 'm7', 'M9', 'P11', 'M13'];
+ var explicitMajor = false;
+
+ function setChord(name) {
+ var intervals = SYMBOLS[name];
+ for (var i = 0, len = intervals.length; i < len; i++) {
+ notes[i + 1] = intervals[i];
+ }
+
+ chordLength = intervals.length;
+ }
+
+ // Remove whitespace, commas and parentheses
+ symbol = symbol.replace(/[,\s\(\)]/g, '');
+ for (var i = 0, len = symbol.length; i < len; i++) {
+ if (!(c = symbol[i]))
+ return;
+
+ if (parsing === 'quality') {
+ var sub3 = (i + 2) < len ? symbol.substr(i, 3).toLowerCase() : null;
+ var sub2 = (i + 1) < len ? symbol.substr(i, 2).toLowerCase() : null;
+ if (sub3 in SYMBOLS)
+ name = sub3;
+ else if (sub2 in SYMBOLS)
+ name = sub2;
+ else if (c in SYMBOLS)
+ name = c;
+ else
+ name = '';
+
+ if (name)
+ setChord(name);
+
+ if (name === 'M' || name === 'ma' || name === 'maj')
+ explicitMajor = true;
+
+
+ i += name.length - 1;
+ parsing = 'extension';
+ } else if (parsing === 'extension') {
+ c = (c === '1' && symbol[i + 1]) ? +symbol.substr(i, 2) : +c;
+
+ if (!isNaN(c) && c !== 6) {
+ chordLength = (c - 1) / 2;
+
+ if (chordLength !== Math.round(chordLength))
+ return new Error('Invalid interval extension: ' + c.toString(10));
+
+ if (name === 'o' || name === 'dim')
+ notes[3] = 'd7';
+ else if (explicitMajor)
+ notes[3] = 'M7';
+
+ i += c >= 10 ? 1 : 0;
+ } else if (c === 6) {
+ notes[3] = 'M6';
+ chordLength = Math.max(3, chordLength);
+ } else
+ i -= 1;
+
+ parsing = 'alterations';
+ } else if (parsing === 'alterations') {
+ var alterations = symbol.substr(i).split(/(#|b|add|maj|sus|M)/i),
+ next, flat = false, sharp = false;
+
+ if (alterations.length === 1)
+ return new Error('Invalid alteration');
+ else if (alterations[0].length !== 0)
+ return new Error('Invalid token: \'' + alterations[0] + '\'');
+
+ var ignore = false;
+ alterations.forEach(function(alt, i, arr) {
+ if (ignore || !alt.length)
+ return ignore = false;
+
+ var next = arr[i + 1], lower = alt.toLowerCase();
+ if (alt === 'M' || lower === 'maj') {
+ if (next === '7')
+ ignore = true;
+
+ chordLength = Math.max(3, chordLength);
+ notes[3] = 'M7';
+ } else if (lower === 'sus') {
+ var type = 'P4';
+ if (next === '2' || next === '4') {
+ ignore = true;
+
+ if (next === '2')
+ type = 'M2';
+ }
+
+ notes[1] = type; // Replace third with M2 or P4
+ } else if (lower === 'add') {
+ if (next === '9')
+ additionals.push('M9');
+ else if (next === '11')
+ additionals.push('P11');
+ else if (next === '13')
+ additionals.push('M13');
+
+ ignore = true
+ } else if (lower === 'b') {
+ flat = true;
+ } else if (lower === '#') {
+ sharp = true;
+ } else {
+ var token = +alt, quality, intPos;
+ if (isNaN(token) || String(token).length !== alt.length)
+ return new Error('Invalid token: \'' + alt + '\'');
+
+ if (token === 6) {
+ if (sharp)
+ notes[3] = 'A6';
+ else if (flat)
+ notes[3] = 'm6';
+ else
+ notes[3] = 'M6';
+
+ chordLength = Math.max(3, chordLength);
+ return;
+ }
+
+ // Calculate the position in the 'note' array
+ intPos = (token - 1) / 2;
+ if (chordLength < intPos)
+ chordLength = intPos;
+
+ if (token < 5 || token === 7 || intPos !== Math.round(intPos))
+ return new Error('Invalid interval alteration: ' + token);
+
+ quality = notes[intPos][0];
+
+ // Alterate the quality of the interval according the accidentals
+ if (sharp) {
+ if (quality === 'd')
+ quality = 'm';
+ else if (quality === 'm')
+ quality = 'M';
+ else if (quality === 'M' || quality === 'P')
+ quality = 'A';
+ } else if (flat) {
+ if (quality === 'A')
+ quality = 'M';
+ else if (quality === 'M')
+ quality = 'm';
+ else if (quality === 'm' || quality === 'P')
+ quality = 'd';
+ }
+
+ sharp = flat = false;
+ notes[intPos] = quality + token;
+ }
+ });
+ parsing = 'ended';
+ } else if (parsing === 'ended') {
+ break;
+ }
+ }
+
+ return notes.slice(0, chordLength + 1).concat(additionals);
+}
+
+},{}],11:[function(require,module,exports){
+var coords = require('notecoord');
+var accval = require('accidental-value');
+
+module.exports = function helmholtz(name) {
+ var name = name.replace(/\u2032/g, "'").replace(/\u0375/g, ',');
+ var parts = name.match(/^(,*)([a-h])(x|#|bb|b?)([,\']*)$/i);
+
+ if (!parts || name !== parts[0])
+ throw new Error('Invalid formatting');
+
+ var note = parts[2];
+ var octaveFirst = parts[1];
+ var octaveLast = parts[4];
+ var lower = note === note.toLowerCase();
+ var octave;
+
+ if (octaveFirst) {
+ if (lower)
+ throw new Error('Invalid formatting - found commas before lowercase note');
+
+ octave = 2 - octaveFirst.length;
+ } else if (octaveLast) {
+ if (octaveLast.match(/^'+$/) && lower)
+ octave = 3 + octaveLast.length;
+ else if (octaveLast.match(/^,+$/) && !lower)
+ octave = 2 - octaveLast.length;
+ else
+ throw new Error('Invalid formatting - mismatch between octave ' +
+ 'indicator and letter case')
+ } else
+ octave = lower ? 3 : 2;
+
+ var accidentalValue = accval.interval(parts[3].toLowerCase());
+ var coord = coords(note.toLowerCase());
+
+ coord[0] += octave;
+ coord[0] += accidentalValue[0] - coords.A4[0];
+ coord[1] += accidentalValue[1] - coords.A4[1];
+
+ return coord;
+};
+
+},{"accidental-value":9,"notecoord":13}],12:[function(require,module,exports){
+var pattern = /^(AA|A|P|M|m|d|dd)(-?\d+)$/;
+
+// The interval it takes to raise a note a semitone
+var sharp = [-4, 7];
+
+var pAlts = ['dd', 'd', 'P', 'A', 'AA'];
+var mAlts = ['dd', 'd', 'm', 'M', 'A', 'AA'];
+
+var baseIntervals = [
+ [0, 0],
+ [3, -5],
+ [2, -3],
+ [1, -1],
+ [0, 1],
+ [3, -4],
+ [2, -2],
+ [1, 0]
+];
+
+module.exports = function(simple) {
+ var parser = simple.match(pattern);
+ if (!parser) return null;
+
+ var quality = parser[1];
+ var number = +parser[2];
+ var sign = number < 0 ? -1 : 1;
+
+ number = sign < 0 ? -number : number;
+
+ var lower = number > 8 ? (number % 7 || 7) : number;
+ var octaves = (number - lower) / 7;
+
+ var base = baseIntervals[lower - 1];
+ var alts = base[0] <= 1 ? pAlts : mAlts;
+ var alt = alts.indexOf(quality) - 2;
+
+ // this happens, if the alteration wasn't suitable for this type
+ // of interval, such as P2 or M5 (no "perfect second" or "major fifth")
+ if (alt === -3) return null;
+
+ return [
+ sign * (base[0] + octaves + sharp[0] * alt),
+ sign * (base[1] + sharp[1] * alt)
+ ];
+}
+
+// Copy to avoid overwriting internal base intervals
+module.exports.coords = baseIntervals.slice(0);
+
+},{}],13:[function(require,module,exports){
+// First coord is octaves, second is fifths. Distances are relative to c
+var notes = {
+ c: [0, 0],
+ d: [-1, 2],
+ e: [-2, 4],
+ f: [1, -1],
+ g: [0, 1],
+ a: [-1, 3],
+ b: [-2, 5],
+ h: [-2, 5]
+};
+
+module.exports = function(name) {
+ return name in notes ? [notes[name][0], notes[name][1]] : null;
+};
+
+module.exports.notes = notes;
+module.exports.A4 = [3, 3]; // Relative to C0 (scientic notation, ~16.35Hz)
+module.exports.sharp = [-4, 7];
+
+},{}],14:[function(require,module,exports){
+module.exports = function(coord, stdPitch) {
+ if (typeof coord === 'number') {
+ stdPitch = coord;
+ return function(coord) {
+ return stdPitch * Math.pow(2, (coord[0] * 12 + coord[1] * 7) / 12);
+ }
+ }
+
+ stdPitch = stdPitch || 440;
+ return stdPitch * Math.pow(2, (coord[0] * 12 + coord[1] * 7) / 12);
+}
+
+},{}],15:[function(require,module,exports){
+var coords = require('notecoord');
+var accval = require('accidental-value');
+
+module.exports = function scientific(name) {
+ var format = /^([a-h])(x|#|bb|b?)(-?\d*)/i;
+
+ var parser = name.match(format);
+ if (!(parser && name === parser[0] && parser[3].length)) return;
+
+ var noteName = parser[1];
+ var octave = +parser[3];
+ var accidental = parser[2].length ? parser[2].toLowerCase() : '';
+
+ var accidentalValue = accval.interval(accidental);
+ var coord = coords(noteName.toLowerCase());
+
+ coord[0] += octave;
+ coord[0] += accidentalValue[0] - coords.A4[0];
+ coord[1] += accidentalValue[1] - coords.A4[1];
+
+ return coord;
+};
+
+},{"accidental-value":9,"notecoord":13}]},{},[1])(1)
+});
ADDED public/teoria-master/test/chords.js
Index: public/teoria-master/test/chords.js
==================================================================
--- public/teoria-master/test/chords.js
+++ public/teoria-master/test/chords.js
@@ -0,0 +1,312 @@
+var vows = require('vows'),
+ assert = require('assert'),
+ teoria = require('../');
+
+vows.describe('Chords').addBatch({
+ 'Chord parser': {
+ 'Emaj7': function() {
+ assert.deepEqual(teoria.chord('Emaj7').simple(), ['e', 'g#', 'b', 'd#']);
+ },
+
+ 'A+': function() {
+ assert.deepEqual(teoria.chord('A+').simple(), ['a', 'c#', 'e#']);
+ },
+
+ 'Bb+': function() {
+ assert.deepEqual(teoria.chord('Bb+').simple(), ['bb', 'd', 'f#']);
+ },
+
+ 'F#maj7': function() {
+ assert.deepEqual(teoria.chord('F#maj7').simple(), ['f#', 'a#', 'c#', 'e#']);
+ },
+
+ 'Hmaj7': function() {
+ assert.deepEqual(teoria.chord('Hmaj7').simple(), ['b', 'd#', 'f#', 'a#']);
+ },
+
+ 'H#maj7': function() {
+ assert.deepEqual(teoria.chord('H#maj7').simple(), ['b#', 'dx', 'fx', 'ax']);
+ },
+
+ 'C7b5': function() {
+ assert.deepEqual(teoria.chord('C7b5').simple(), ['c', 'e', 'gb', 'bb']);
+ },
+
+ 'Eb7b5': function() {
+ assert.deepEqual(teoria.chord('Eb7b5').simple(), ['eb', 'g', 'bbb', 'db']);
+ },
+
+ 'D#7b5': function() {
+ assert.deepEqual(teoria.chord('D#7b5').simple(), ['d#', 'fx', 'a', 'c#']);
+ },
+
+ 'C9': function() {
+ assert.deepEqual(teoria.chord('C9').simple(), ['c', 'e', 'g', 'bb', 'd']);
+ },
+
+ 'Eb9': function() {
+ assert.deepEqual(teoria.chord('Eb9').simple(), ['eb', 'g', 'bb', 'db', 'f']);
+ },
+
+ 'G#(#9)': function() {
+ assert.deepEqual(teoria.chord('G#(#9)').simple(), ['g#', 'b#', 'd#', 'f#', 'ax']);
+ },
+
+ 'Ab(b9)': function() {
+ assert.deepEqual(teoria.chord('Ab(b9)').simple(), ['ab', 'c', 'eb', 'gb', 'bbb']);
+ },
+
+ 'F#(#11)': function() {
+ assert.deepEqual(teoria.chord('F#(#11)').simple(), ['f#', 'a#', 'c#', 'e', 'g#', 'b#']);
+ },
+
+ 'Ab13': function() {
+ assert.deepEqual(teoria.chord('Ab13').simple(), ['ab', 'c', 'eb', 'gb', 'bb', 'db', 'f']);
+ },
+
+ 'C7sus4': function() {
+ assert.deepEqual(teoria.chord('C7sus4').simple(), ['c', 'f', 'g', 'bb']);
+ },
+
+ 'Cmaj9': function() {
+ assert.deepEqual(teoria.chord('Cmaj9').simple(), ['c', 'e', 'g', 'b', 'd']);
+ },
+
+ 'Dmb6': function() {
+ assert.deepEqual(teoria.chord('Dmb6').simple(), ['d', 'f', 'a', 'bb']);
+ },
+
+ 'C#(#5#9)': function() {
+ assert.deepEqual(teoria.chord('C#(#5#9)').simple(), ['c#', 'e#', 'gx', 'b', 'dx']);
+ },
+
+ 'Cm(maj7)': function() {
+ assert.deepEqual(teoria.chord('Cm(maj7)').simple(), ['c', 'eb', 'g', 'b']);
+ },
+
+ 'F#m(11b5b9)': function() {
+ assert.deepEqual(teoria.chord('F#m(11b5b9)').simple(), ['f#', 'a', 'c', 'e', 'g', 'b']);
+ },
+
+ 'C/e': function() {
+ assert.deepEqual(teoria.chord('C/e').simple(), ['e', 'c', 'g']);
+ },
+
+ 'A7/g': function() {
+ assert.deepEqual(teoria.chord('A7/g').simple(), ['g', 'a', 'c#', 'e']);
+ },
+
+ 'G/f#': function() {
+ assert.deepEqual(teoria.chord('G/f#').simple(), ['f#', 'g', 'b', 'd']);
+ },
+
+ 'C6': function() {
+ assert.deepEqual(teoria.chord('C6').simple(), ['c', 'e', 'g', 'a']);
+ },
+
+ 'A#6': function() {
+ assert.deepEqual(teoria.chord('A#6').simple(), ['a#', 'cx', 'e#', 'fx']);
+ },
+
+ 'Bb6': function() {
+ assert.deepEqual(teoria.chord('Bb6').simple(), ['bb', 'd', 'f', 'g']);
+ },
+
+ 'Am6': function() {
+ assert.deepEqual(teoria.chord('Am6').simple(), ['a', 'c', 'e', 'f#']);
+ },
+
+ 'D(#6)': function() {
+ assert.deepEqual(teoria.chord('D(#6)').simple(), ['d', 'f#', 'a', 'b#']);
+ },
+
+ 'Eo': function() {
+ assert.deepEqual(teoria.chord('Eo').simple(), ['e', 'g', 'bb']);
+ },
+
+ 'Eø': function() {
+ assert.deepEqual(teoria.chord('Eø').simple(), ['e', 'g', 'bb', 'd']);
+ },
+
+ 'Do': function() {
+ assert.deepEqual(teoria.chord('Do').simple(), ['d', 'f', 'ab']);
+ },
+
+ 'Dø': function() {
+ assert.deepEqual(teoria.chord('Dø').simple(), ['d', 'f', 'ab', 'c']);
+ },
+
+ 'Fo7': function() {
+ assert.deepEqual(teoria.chord('Fo7').simple(), ['f', 'ab', 'cb', 'ebb']);
+ },
+
+ 'G#ø7': function() {
+ assert.deepEqual(teoria.chord('G#ø7').simple(), ['g#', 'b', 'd', 'f#']);
+ },
+
+ 'Cmin': function() {
+ assert.deepEqual(teoria.chord('Cmin').simple(), ['c', 'eb', 'g']);
+ },
+
+ 'Bmin11': function() {
+ assert.deepEqual(teoria.chord('Bmin11').simple(), ['b', 'd', 'f#', 'a', 'c#', 'e']);
+ },
+
+ 'C+M7': function() {
+ assert.deepEqual(teoria.chord('C+M7').simple(), ['c', 'e', 'g#', 'b']);
+ },
+
+ 'Bbdom7b5': function() {
+ assert.deepEqual(teoria.chord('Bbdom7b5').simple(), ['bb', 'd', 'fb', 'ab']);
+ },
+
+ 'E5': function() {
+ assert.deepEqual(teoria.chord('E5').simple(), ['e', 'b']);
+ },
+
+ 'A5': function() {
+ assert.deepEqual(teoria.chord('A5').simple(), ['a', 'e']);
+ },
+
+ 'C13#9b5': function() {
+ assert.deepEqual(teoria.chord('C13#9b5').simple(), ['c', 'e', 'gb', 'bb', 'd#', 'f', 'a']);
+ },
+
+ 'D13#5b9': function() {
+ assert.deepEqual(teoria.chord('D13#5b9').simple(), ['d', 'f#', 'a#', 'c', 'eb', 'g', 'b']);
+ },
+
+ 'C6/9': function() {
+ assert.deepEqual(teoria.chord('C6/9').simple(), ['c', 'e', 'g', 'a', 'd']);
+ },
+
+ 'Ab6/9': function() {
+ assert.deepEqual(teoria.chord('Ab6/9').simple(), ['ab', 'c', 'eb', 'f', 'bb']);
+ },
+
+ 'CM7': function() {
+ assert.deepEqual(teoria.chord('CM7').simple(), ['c', 'e', 'g', 'b']);
+ },
+
+ 'CmM7': function() {
+ assert.deepEqual(teoria.chord('CmM7').simple(), ['c', 'eb', 'g', 'b']);
+ },
+
+ 'DM': function() {
+ assert.deepEqual(teoria.chord('DM').simple(), ['d', 'f#', 'a']);
+ },
+
+ 'EM#5': function() {
+ assert.deepEqual(teoria.chord('EM#5').simple(), ['e', 'g#', 'b#']);
+ },
+
+ 'FM9': function() {
+ assert.deepEqual(teoria.chord('FM9').simple(), ['f', 'a', 'c', 'e', 'g']);
+ },
+
+ 'Dmi': function() {
+ assert.deepEqual(teoria.chord('Dmi').simple(), ['d', 'f', 'a']);
+
+ },
+
+ 'Emi7': function() {
+ assert.deepEqual(teoria.chord('Emi7').simple(), ['e', 'g', 'b', 'd']);
+ },
+
+ 'Dma': function() {
+ assert.deepEqual(teoria.chord('Dma').simple(), ['d', 'f#', 'a']);
+ },
+
+ 'Ema9': function() {
+ assert.deepEqual(teoria.chord('Ema9').simple(), ['e', 'g#', 'b', 'd#', 'f#']);
+ }
+ },
+
+ 'Case doesn\'t matter': {
+ 'BbDom': function() {
+ assert.deepEqual(teoria.chord('BbDom').simple(), teoria.chord('Bbdom').simple());
+ },
+
+ 'EbMaj9': function() {
+ assert.deepEqual(teoria.chord('EbMaj9').simple(), teoria.chord('Ebmaj9').simple());
+ },
+
+ 'ASus4': function() {
+ assert.deepEqual(teoria.chord('ASus4').simple(), teoria.chord('Asus4').simple());
+ },
+
+ 'EAdd9': function() {
+ assert.deepEqual(teoria.chord('EAdd9').simple(), teoria.chord('Eadd9').simple());
+ }
+ },
+
+ 'Chord Methods': {
+ '#bass of Cmaj7': function() {
+ assert.equal(teoria.chord('Cmaj7').bass().toString(true), 'c');
+ },
+
+ '#bass of A/C#': function() {
+ assert.equal(teoria.chord('A/C#').bass().toString(true), 'c#');
+ },
+
+ '#bass of D6/9': function() {
+ assert.equal(teoria.chord('D6/9').bass().toString(true), 'd');
+ },
+
+ '#quality() of Bmaj7': function() {
+ assert.equal(teoria.chord('Bmaj7').quality(), 'major');
+ },
+
+ '#quality() of E7': function() {
+ assert.equal(teoria.chord('E7').quality(), 'dominant');
+ },
+
+ '#quality() of Dbm7b5': function() {
+ assert.equal(teoria.chord('Dbm7b5').quality(), 'half-diminished');
+ },
+
+ '#quality() of Cmin11': function() {
+ assert.equal(teoria.chord('Cmin11').quality(), 'minor');
+ },
+
+ '#quality() of A+': function() {
+ assert.equal(teoria.chord('A+').quality(), 'augmented');
+ },
+
+ '#quality() of A#(b13)': function() {
+ assert.equal(teoria.chord('A#(b13)').quality(), 'dominant');
+ },
+
+ '#quality() of Gmb5': function() {
+ assert.equal(teoria.chord('Gmb5').quality(), 'diminished');
+ },
+
+ '#quality() of Asus4': function() {
+ assert.equal(teoria.chord('Asus4').quality(), undefined);
+ },
+
+ '#quality() of Fm#5': function() {
+ assert.equal(teoria.chord('Fm#5').quality(), 'minor');
+ },
+
+ '#chordType() of C': function() {
+ assert.equal(teoria.chord('C').chordType(), 'triad');
+ },
+
+ '#chordType() of Dm': function() {
+ assert.equal(teoria.chord('Dm').chordType(), 'triad');
+ },
+
+ '#chordType() of A7': function() {
+ assert.equal(teoria.chord('A7').chordType(), 'tetrad');
+ },
+
+ '#chordType() of Bsus4': function() {
+ assert.equal(teoria.chord('Bsus4').chordType(), 'trichord');
+ },
+
+ '#chordType() of E5': function() {
+ assert.equal(teoria.chord('E5').chordType(), 'dyad');
+ },
+ }
+}).export(module);
ADDED public/teoria-master/test/intervals.js
Index: public/teoria-master/test/intervals.js
==================================================================
--- public/teoria-master/test/intervals.js
+++ public/teoria-master/test/intervals.js
@@ -0,0 +1,487 @@
+var vows = require('vows'),
+ assert = require('assert'),
+ teoria = require('../');
+
+function addSimple(interval1, interval2) {
+ return teoria.interval(interval1).add(teoria.interval(interval2));
+}
+
+vows.describe('Intervals').addBatch({
+ 'Relative Intervals': {
+ topic: function() {
+ return teoria.note('F#,');
+ },
+
+ 'Doubly diminished second': function(note) {
+ assert.deepEqual(note.interval('dd2'), teoria.note('Gbb,'));
+ },
+
+ 'Diminished second': function(note) {
+ assert.deepEqual(note.interval('d2'), teoria.note('Gb,'));
+ },
+
+ 'Diminished second, API method two': function(note) {
+ assert.deepEqual(teoria.interval(note, teoria.interval('d2')), teoria.note('Gb,'));
+ },
+
+ 'Diminished second, API method three': function(note) {
+ assert.deepEqual(note.interval(teoria.interval('d2')), teoria.note('Gb,'));
+ },
+
+ 'Minor second': function(note) {
+ assert.deepEqual(note.interval('m2'), teoria.note('G,'));
+ },
+
+ 'Major second': function(note) {
+ assert.deepEqual(note.interval('M2'), teoria.note('G#,'));
+ },
+
+ 'Augmented second': function(note) {
+ assert.deepEqual(note.interval('A2'), teoria.note('Gx,'));
+ },
+
+ 'Doubly diminished third': function(note) {
+ assert.deepEqual(note.interval('dd3'), teoria.note('Abb,'));
+ },
+
+ 'Diminished third': function(note) {
+ assert.deepEqual(note.interval('d3'), teoria.note('Ab,'));
+ },
+
+ 'Minor third': function(note) {
+ assert.deepEqual(note.interval('m3'), teoria.note('A,'));
+ },
+
+ 'Major third': function(note) {
+ assert.deepEqual(note.interval('M3'), teoria.note('A#,'));
+ },
+
+ 'Augmented third': function(note) {
+ assert.deepEqual(note.interval('A3'), teoria.note('Ax,'));
+ },
+
+ 'Doubly diminished fourth': function(note) {
+ assert.deepEqual(note.interval('dd4'), teoria.note('Bbb,'));
+ },
+
+ 'Diminished fourth': function(note) {
+ assert.deepEqual(note.interval('d4'), teoria.note('Bb,'));
+ },
+
+ 'Perfect fourth': function(note) {
+ assert.deepEqual(note.interval('P4'), teoria.note('B,'));
+ },
+
+ 'Augmented fourth': function(note) {
+ assert.deepEqual(note.interval('A4'), teoria.note('B#,'));
+ },
+
+ 'Doubly augmented fourth': function(note) {
+ assert.deepEqual(note.interval('AA4'), teoria.note('Bx,'));
+ },
+
+ 'Doubly diminished fifth': function(note) {
+ assert.deepEqual(note.interval('dd5'), teoria.note('Cb'));
+ },
+
+ 'Diminished fifth': function(note) {
+ assert.deepEqual(note.interval('d5'), teoria.note('C'));
+ },
+
+ 'Perfect fifth': function(note) {
+ assert.deepEqual(note.interval('P5'), teoria.note('C#'));
+ },
+
+ 'Augmented fifth': function(note) {
+ assert.deepEqual(note.interval('A5'), teoria.note('Cx'));
+ },
+
+ 'Doubly diminished sixth': function(note) {
+ assert.deepEqual(note.interval('dd6'), teoria.note('Dbb'));
+ },
+
+ 'Diminished sixth': function(note) {
+ assert.deepEqual(note.interval('d6'), teoria.note('Db'));
+ },
+
+ 'Minor sixth': function(note) {
+ assert.deepEqual(note.interval('m6'), teoria.note('D'));
+ },
+
+ 'Major sixth': function(note) {
+ assert.deepEqual(note.interval('M6'), teoria.note('D#'));
+ },
+
+ 'Augmented sixth': function(note) {
+ assert.deepEqual(note.interval('A6'), teoria.note('Dx'));
+ },
+
+ 'Doubly diminished seventh': function(note) {
+ assert.deepEqual(note.interval('dd7'), teoria.note('Ebb'));
+ },
+
+ 'Diminished seventh': function(note) {
+ assert.deepEqual(note.interval('d7'), teoria.note('Eb'));
+ },
+
+ 'Minor seventh': function(note) {
+ assert.deepEqual(note.interval('m7'), teoria.note('E'));
+ },
+
+ 'Major seventh': function(note) {
+ assert.deepEqual(note.interval('M7'), teoria.note('E#'));
+ },
+
+ 'Augmented seventh': function(note) {
+ assert.deepEqual(note.interval('A7'), teoria.note('Ex'));
+ },
+
+ 'Doubly diminished octave': function(note) {
+ assert.deepEqual(note.interval('dd8'), teoria.note('Fb'));
+ },
+
+ 'Diminished octave': function(note) {
+ assert.deepEqual(note.interval('d8'), teoria.note('F'));
+ },
+
+ 'Perfect octave': function(note) {
+ assert.deepEqual(note.interval('P8'), teoria.note('F#'));
+ },
+
+ 'Augmented octave': function(note) {
+ assert.deepEqual(note.interval('A8'), teoria.note('Fx'));
+ },
+
+ 'Minor ninth': function(note) {
+ assert.deepEqual(note.interval('m9'), teoria.note('G'));
+ },
+
+ 'Major ninth': function(note) {
+ assert.deepEqual(note.interval('M9'), teoria.note('G#'));
+ },
+
+ 'Minor tenth': function(note) {
+ assert.deepEqual(note.interval('m10'), teoria.note('A'));
+ },
+
+ 'Major tenth': function(note) {
+ assert.deepEqual(note.interval('M10'), teoria.note('A#'));
+ },
+
+ 'Perfect eleventh': function(note) {
+ assert.deepEqual(note.interval('P11'), teoria.note('B'));
+ },
+
+ 'Diminished twelfth': function(note) {
+ assert.deepEqual(note.interval('d12'), teoria.note('c'));
+ },
+
+ 'Perfect twelfth': function(note) {
+ assert.deepEqual(note.interval('P12'), teoria.note('c#'));
+ },
+
+ 'Minor thirteenth': function(note) {
+ assert.deepEqual(note.interval('m13'), teoria.note('d'));
+ },
+
+ 'Major thirteenth': function(note) {
+ assert.deepEqual(note.interval('M13'), teoria.note('d#'));
+ },
+
+ 'Minor fourteenth': function(note) {
+ assert.deepEqual(note.interval('m14'), teoria.note('e'));
+ },
+
+ 'Major fourteenth': function(note) {
+ assert.deepEqual(note.interval('M14'), teoria.note('e#'));
+ },
+
+ 'Doubly diminished second up': function() {
+ assert.deepEqual(teoria.note('e').interval(teoria.note('fbb')),
+ teoria.interval('dd2'));
+ },
+
+ 'Doubly diminished second down': function() {
+ assert.deepEqual(teoria.note('f').interval(teoria.note('ex')),
+ teoria.interval('dd-2'));
+ }
+ },
+
+ 'Interval descending': {
+ 'A major third down from E4': function() {
+ assert.deepEqual(teoria.note('E4').interval('M-3'), teoria.note('C4'));
+ },
+
+ 'Minor second down from C2': function() {
+ assert.deepEqual(teoria.note('C2').interval('m-2'), teoria.note('B1'));
+ },
+
+ 'A diminished fifth down from Eb5': function() {
+ assert.deepEqual(teoria.note('Eb5').interval('d-5'), teoria.note('A4'));
+ },
+
+ 'A major ninth down from G#4': function() {
+ assert.deepEqual(teoria.note('G#4').interval('M-9'), teoria.note('F#3'));
+ },
+
+ 'An augmented sixth down from Bb4': function() {
+ assert.deepEqual(teoria.note('Bb4').interval('A-6'), teoria.note('Dbb4'));
+ }
+ },
+
+ 'Interval inversions': {
+ 'Invert m2 is M7': function() {
+ assert.equal(teoria.interval.invert('m2'), 'M7');
+ },
+
+ 'Invert M2 is m7': function() {
+ assert.equal(teoria.interval.invert('M2'), 'm7');
+ },
+
+ 'Invert m3 is M6': function() {
+ assert.equal(teoria.interval.invert('m3'), 'M6');
+ },
+
+ 'Invert M3 is m6': function() {
+ assert.equal(teoria.interval.invert('M3'), 'm6');
+ },
+
+ 'Invert P4 is P5': function() {
+ assert.equal(teoria.interval.invert('P4'), 'P5');
+ },
+
+ 'Invert A5 is d4': function() {
+ assert.equal(teoria.interval.invert('A5'), 'd4');
+ }
+ },
+
+ 'Interval base': {
+ 'Base of d5 is a fifth': function() {
+ assert.equal(teoria.interval('d5').base(), 'fifth');
+ },
+
+ 'Base of A7 is a seventh': function() {
+ assert.equal(teoria.interval('A7').base(), 'seventh');
+ },
+
+ 'Base of m2 is a second': function() {
+ assert.equal(teoria.interval('m2').base(), 'second');
+ },
+
+ 'Base of M6 is a sixth': function() {
+ assert.equal(teoria.interval('M6').base(), 'sixth');
+ },
+
+ 'Base of dd8 is an octave': function() {
+ assert.equal(teoria.interval('dd8').base(), 'octave');
+ },
+
+ 'Base of AA4 is a fourth': function() {
+ assert.equal(teoria.interval('AA4').base(), 'fourth');
+ },
+
+ 'Base of d-5 is a fifth': function() {
+ assert.equal(teoria.interval('d-5').base(), 'fifth');
+ },
+
+ 'Base of m-9 is a second': function() {
+ assert.equal(teoria.interval('m-2').base(), 'second');
+ },
+
+ 'Base of M-13 is a sixth': function() {
+ assert.equal(teoria.interval('M-13').base(), 'sixth');
+ },
+
+ 'Base of P-11 is a fourth': function() {
+ assert.equal(teoria.interval('P-11').base(), 'fourth');
+ },
+
+ 'Base of AA-7 is a seventh': function() {
+ assert.equal(teoria.interval('AA-7').base(), 'seventh');
+ }
+ },
+
+ 'Compound Intervals': {
+ 'A major seventeenth is a compound interval': function() {
+ assert.equal(teoria.interval('M17').isCompound(), true);
+ },
+
+ 'A major seventeenth\'s simple part is a major third': function() {
+ assert.equal(teoria.interval('M17').simple(), 'M3');
+ },
+
+ 'A descending major fourteenth\'s simple part is a descending major seventh': function() {
+ assert.equal(teoria.interval('M-14').simple(), 'M-7');
+ },
+
+ 'A perfect nineteenth\'s simple part is equal to a perfect fifth': function() {
+ assert.equal(teoria.interval('P19').simple().equal(teoria.interval('P5')), true);
+ },
+
+ 'A perfect nineteenth\'s simple part is not equal to a major sixth': function() {
+ assert.equal(teoria.interval('P19').simple().equal(teoria.interval('M6')), false);
+ },
+
+ 'A descending augmented ninth\'s simple part is equal to a descending augmented second': function() {
+ assert.equal(teoria.interval('A-9').simple().equal(teoria.interval('A-2')), true);
+ },
+
+ 'A 22nd has two compound octaves': function() {
+ assert.equal(teoria.interval('P22').octaves(), 2);
+ },
+
+ 'A descending fourth has no compound octaves': function() {
+ assert.equal(teoria.interval('P-4').octaves(), 0);
+ },
+
+ 'A descending eleventh has one compound octave': function() {
+ assert.equal(teoria.interval('P-11').octaves(), 1);
+ },
+
+ 'A descending augmented third has no compound octaves': function() {
+ assert.equal(teoria.interval('A-3').octaves(), 0);
+ },
+
+ 'A descending major 16th has two compound octaves': function() {
+ assert.equal(teoria.interval('M-16').octaves(), 2);
+ },
+
+ 'A major seventh is greater than a minor seventh': function() {
+ assert.equal(teoria.interval('M7').greater(teoria.interval('m7')), true);
+ },
+
+ 'An augmented octave is smaller than a major ninth': function() {
+ assert.equal(teoria.interval('A8').smaller(teoria.interval('M9')), true);
+ },
+
+ 'A major third is equal to another major third': function() {
+ assert.equal(teoria.interval('M3').equal(teoria.interval('M3')), true);
+ },
+
+ 'An augmented fifth is not equal to a minor sixth': function() {
+ assert.equal(teoria.interval('P5').equal(teoria.interval('m6')), false);
+ },
+
+ 'A perfect fifth is not equal to a perfect octave': function() {
+ assert.equal(teoria.interval('P5').equal(teoria.interval('P8')), false);
+ },
+
+ 'The simple part of a major 23th is a major second': function() {
+ assert.equal(teoria.interval('M23').simple(), 'M2');
+ }
+ },
+
+ 'Interval direction': {
+ 'A3 to C4 is up': function() {
+ assert.equal(teoria.note('A3').interval(teoria.note('C4')).direction(), 'up');
+ },
+
+ 'Bb5 to Bb5 is up (a unison is always up)': function() {
+ assert.equal(teoria.note('Bb5').interval(teoria.note('Bb5')).direction(), 'up');
+ },
+
+ 'G#4 to D4 is down': function() {
+ assert.equal(teoria.note('G#4').interval(teoria.note('D4')).direction(), 'down');
+ },
+
+ 'F6 to E6 is down': function() {
+ assert.equal(teoria.note('F6').interval(teoria.note('E6')).direction(), 'down');
+ },
+
+ 'C4 to A3 is up, w. direction set to up': function() {
+ assert.equal(teoria.note('C4').interval(teoria.note('A3')).direction('up').direction(), 'up');
+ },
+
+ 'A3 to C4 remains up w. direction set to up': function() {
+ assert.equal(teoria.note('A3').interval(teoria.note('C4')).direction('up').direction(), 'up');
+ },
+
+ 'm2 is up': function() {
+ assert.equal(teoria.interval('m2').direction(), 'up');
+ },
+
+ 'P11 is up': function() {
+ assert.equal(teoria.interval('P11').direction(), 'up');
+ },
+
+ 'P1 is up': function() {
+ assert.equal(teoria.interval('P1').direction(), 'up');
+ },
+
+ 'A1 is up': function() {
+ assert.equal(teoria.interval('A1').direction(), 'up');
+ },
+
+ 'd1 is up': function() {
+ assert.equal(teoria.interval('d1').direction(), 'up');
+ },
+
+ 'm-2 is down': function() {
+ assert.equal(teoria.interval('m-2').direction(), 'down');
+ },
+
+ 'M-17 is down': function() {
+ assert.equal(teoria.interval('M-17').direction(), 'down');
+ },
+
+ 'd-2 is down': function() {
+ assert.equal(teoria.interval('d-2').direction(), 'down');
+ },
+
+ 'dd-2 is down (although it is up)': function() {
+ assert.equal(teoria.interval('dd-2').direction(), 'down');
+ },
+
+ 'A-2 is down': function() {
+ assert.equal(teoria.interval('A-2').direction(), 'down');
+ },
+
+ 'd-1 is up (all unison values are up)': function() {
+ assert.equal(teoria.interval('d-1').direction(), 'up');
+ },
+
+ 'A-1 is up (all unison values are up)': function() {
+ assert.equal(teoria.interval('A-1').direction(), 'up');
+ }
+ },
+
+ 'Interval arithmetic': {
+ 'm3 + M2 = P4': function() {
+ assert.equal(addSimple('m3', 'M2').toString(), 'P4');
+ },
+
+ 'P4 + P5 = P8': function() {
+ assert.equal(addSimple('P4', 'P5').toString(), 'P8');
+ },
+
+ 'M6 + A4 = A9': function() {
+ assert.equal(addSimple('M6', 'A4').toString(), 'A9');
+ },
+
+ 'M-2 + m-2 = m-3': function() {
+ assert.equal(addSimple('M-2', 'm-2').toString(), 'm-3');
+ },
+
+ 'A11 + M9 = A19': function() {
+ assert.equal(addSimple('A11', 'M9').toString(), 'A19');
+ },
+
+ 'm-10 + P4 = m-7': function() {
+ assert.equal(addSimple('m-10', 'P4').toString(), 'm-7');
+ }
+ },
+
+ 'Theoretical intervals - Triple augmented': {
+ topic: function() {
+ return teoria.note('F').interval(teoria.note('Bx'));
+ },
+
+ 'F to Bx has quality value = 3 (triple augmented)': function(interval) {
+ assert.equal(interval.qualityValue(), 3);
+ },
+
+ '#simple() works': function(interval) {
+ assert.deepEqual(interval.simple().coord, [-11, 20]);
+ }
+ }
+}).export(module);
ADDED public/teoria-master/test/notes.js
Index: public/teoria-master/test/notes.js
==================================================================
--- public/teoria-master/test/notes.js
+++ public/teoria-master/test/notes.js
@@ -0,0 +1,307 @@
+var vows = require('vows'),
+ assert = require('assert'),
+ teoria = require('../');
+
+vows.describe('TeoriaNote class').addBatch({
+ 'A4 - a\'': {
+ topic: function() {
+ return teoria.note('A4');
+ },
+
+ 'Octave should be 4': function(note) {
+ assert.equal(note.octave(), 4);
+ },
+
+ 'Note name is lower case': function(note) {
+ assert.equal(note.name(), 'a');
+ },
+
+ 'A4 is the 49th piano key': function(note) {
+ assert.equal(note.key(), 49);
+ },
+
+ 'A4 is expressed a\' in Helmholtz notation': function(note) {
+ assert.equal(note.helmholtz(), 'a\'');
+ },
+
+ 'A4 is expressed A4 in scientific notation': function(note) {
+ assert.equal(note.scientific(), 'A4');
+ },
+
+ 'The frequency of A4 is 440hz': function(note) {
+ assert.equal(note.fq(), 440);
+ }
+ },
+
+ 'C#5 - c#\'\'': {
+ topic: function() {
+ return teoria.note('c#\'\'');
+ },
+
+ 'Octave should be 5': function(note) {
+ assert.equal(note.octave(), 5);
+ },
+
+ 'The name attribute of c# is just c': function(note) {
+ assert.equal(note.name(), 'c');
+ },
+
+ 'The accidental.sign attribute is #': function(note) {
+ assert.equal(note.accidental(), '#');
+ },
+
+ 'The accidental.value attribute is 1': function(note) {
+ assert.equal(note.accidentalValue(), 1);
+ },
+
+ 'C#5 is the 53rd piano key': function(note) {
+ assert.equal(note.key(), 53);
+ },
+
+ 'C#5 is c#\'\' in Helmholtz notation': function(note) {
+ assert.equal(note.helmholtz(), 'c#\'\'');
+ },
+
+ 'c#\'\' is C#5 in scientific notation': function(note) {
+ assert.equal(note.scientific(), 'C#5');
+ },
+
+ 'The frequency of C#5 is approximately 554.365': function(note) {
+ assert.equal(note.fq(), 554.3652619537442);
+ },
+
+ 'The interval between C#5 and A4 is a major third': function(note) {
+ var a4 = teoria.note('A4');
+
+ assert.deepEqual(note.interval(a4), teoria.interval('M-3'));
+ },
+
+ 'The interval between C#5 and Eb6 is diminished tenth': function(note) {
+ var eb6 = teoria.note('Eb6');
+
+ assert.deepEqual(note.interval(eb6), teoria.interval('d10'));
+ },
+
+ 'An diminished fifth away from C#5 is G5': function(note) {
+ var g5 = teoria.note('G5');
+
+ assert.deepEqual(note.interval('d5'), g5);
+ },
+
+ 'The interval between C#4 and Db4 is a diminished second': function() {
+ var cis4 = teoria.note('c#4');
+ var db4 = teoria.note('db4');
+
+ assert.deepEqual(cis4.interval(db4), teoria.interval('d2'));
+ }
+ },
+
+ 'Instantiate with coords': {
+ '[0, 0] is A4': function() {
+ assert.equal(teoria.note([0, 0]).scientific(), 'A4');
+ },
+
+ '[-4, 4] is C#3': function() {
+ assert.equal(teoria.note([-4, 4]).scientific(), 'C#3');
+ },
+
+ '[3, -4] is F5': function() {
+ assert.equal(teoria.note([3, -4]).scientific(), 'F5');
+ },
+
+ '[4, -7] is Ab4': function() {
+ assert.equal(teoria.note([4, -7]).scientific(), 'Ab4');
+ }
+ },
+
+ 'Instantiate from key': {
+ '#49 is A4': function() {
+ assert.equal(teoria.note.fromKey(49).scientific(), 'A4');
+ },
+
+ '#20 is E2': function() {
+ assert.equal(teoria.note.fromKey(20).scientific(), 'E2');
+ },
+
+ '#57 is F5': function() {
+ assert.equal(teoria.note.fromKey(57).scientific(), 'F5');
+ },
+
+ '#72 is G#6': function() {
+ assert.equal(teoria.note.fromKey(72).scientific(), 'G#6');
+ }
+ },
+
+ 'Instantiate from frequency': {
+ '391.995Hz is G4': function() {
+ assert.equal(teoria.note.fromFrequency(391.995).note.scientific(), 'G4');
+ },
+
+ '220.000Hz is A3': function() {
+ assert.equal(teoria.note.fromFrequency(220.000).note.scientific(), 'A3');
+ },
+
+ '155.563Hz is Eb3': function() {
+ assert.equal(teoria.note.fromFrequency(155.563).note.scientific(), 'Eb3');
+ },
+
+ '2959.96Hz is F#7': function() {
+ assert.equal(teoria.note.fromFrequency(2959.96).note.scientific(), 'F#7');
+ }
+ },
+
+ 'Instantiate from MIDI': {
+ 'MIDI#36 is C2': function() {
+ assert.equal(teoria.note.fromMIDI(36).scientific(), 'C2');
+ },
+
+ 'MIDI#77 is F5': function() {
+ assert.equal(teoria.note.fromMIDI(77).scientific(), 'F5');
+ },
+
+ 'MIDI#61 is Db4': function() {
+ assert.equal(teoria.note.fromMIDI(61).scientific(), 'Db4');
+ },
+
+ 'MIDI#80 is G#5': function() {
+ assert.equal(teoria.note.fromMIDI(80).scientific(), 'G#5');
+ }
+ },
+
+ 'Return MIDI note number': {
+ 'MIDI#36 is C2': function() {
+ assert.equal(teoria.note('C2').midi(), 36);
+ },
+
+ 'MIDI#77 is F5': function() {
+ assert.equal(teoria.note('F5').midi(), 77);
+ },
+
+ 'MIDI#61 is Db4': function() {
+ assert.equal(teoria.note('Db4').midi(), 61);
+ },
+
+ 'MIDI#80 is G#5': function() {
+ assert.equal(teoria.note('G#5').midi(), 80);
+ }
+ },
+
+ 'Chroma': {
+ 'C has chroma 0': function() {
+ assert.equal(teoria.note('c').chroma(), 0);
+ },
+
+ 'C# has chroma 1': function() {
+ assert.equal(teoria.note('c#').chroma(), 1);
+ },
+
+ 'B has chroma 11': function() {
+ assert.equal(teoria.note('b').chroma(), 11);
+ },
+
+ 'Db has chroma 1': function() {
+ assert.equal(teoria.note('db').chroma(), 1);
+ },
+
+ 'Dbb has chroma 0': function() {
+ assert.equal(teoria.note('dbb').chroma(), 0);
+ },
+
+ 'E has chroma 4': function() {
+ assert.equal(teoria.note('e').chroma(), 4);
+ },
+
+ 'F has chroma 5': function() {
+ assert.equal(teoria.note('f').chroma(), 5);
+ },
+
+ 'Fb has chroma 4': function() {
+ assert.equal(teoria.note('fb').chroma(), 4);
+ },
+
+ 'H# has chroma 0': function() {
+ assert.equal(teoria.note('h#').chroma(), 0);
+ },
+
+ 'Bx has chroma 1': function() {
+ assert.equal(teoria.note('bx').chroma(), 1);
+ },
+
+ 'Cbb has chroma 10': function() {
+ assert.equal(teoria.note('cbb').chroma(), 10);
+ }
+ },
+
+ 'Scale Degrees': {
+ 'Eb is scale degree 1 (tonic) in an Eb minor scale': function() {
+ var note = teoria.note('eb');
+ assert.equal(note.scaleDegree(teoria.scale('eb', 'major')), 1);
+ },
+
+ 'E is scale degree 3 in a C# dorian': function() {
+ var note = teoria.note('e');
+ assert.equal(note.scaleDegree(teoria.scale('c#', 'dorian')), 3);
+ },
+
+ 'C is scale degree 0 in a D major scale (not in scale)': function() {
+ var note = teoria.note('c');
+ assert.equal(note.scaleDegree(teoria.scale('d', 'major')), 0);
+ },
+
+ 'Bb is scale degree 7 in a C minor': function() {
+ var note = teoria.note('bb');
+ assert.equal(note.scaleDegree(teoria.scale('c', 'minor')), 7);
+ },
+
+ 'Db is scale degree 4 in an Ab major scale': function() {
+ var note = teoria.note('db');
+ assert.equal(note.scaleDegree(teoria.scale('ab', 'major')), 4);
+ },
+
+ 'A# is scale degree 0 in a G minor scale': function() {
+ var note = teoria.note('a#');
+ assert.equal(note.scaleDegree(teoria.scale('g', 'minor')), 0);
+ }
+ },
+
+ 'Enharmonics': {
+ 'c is enharmonic with dbb and b#': function() {
+ assert.deepEqual(teoria.note('c4').enharmonics(),
+ ['dbb4', 'b#3'].map(teoria.note));
+ },
+
+ 'fb is enharmonic with e and dx': function() {
+ assert.deepEqual(teoria.note('fb4').enharmonics(),
+ ['e4', 'dx4'].map(teoria.note));
+ },
+
+ 'cb is enharmonic with ax and b': function() {
+ assert.deepEqual(teoria.note('cb4').enharmonics(),
+ ['b3', 'ax3'].map(teoria.note));
+ }
+ },
+
+ 'Enharmonics with only one accidental': {
+ 'c is enharmonic with b#': function() {
+ assert.deepEqual(teoria.note('c4').enharmonics(true),
+ ['b#3'].map(teoria.note));
+ },
+
+ 'fb is enharmonic with e': function() {
+ assert.deepEqual(teoria.note('fb4').enharmonics(true),
+ ['e4'].map(teoria.note));
+ },
+
+ 'cb is enharmonic with b': function() {
+ assert.deepEqual(teoria.note('cb4').enharmonics(true),
+ ['b3'].map(teoria.note));
+ }
+ },
+
+ 'copy duration on interval': {
+ 'stay whole note on call interval': function() {
+ var note = teoria.note('a#', { duration: 1 });
+ assert.equal(note.duration.value, note.interval('P5').duration.value);
+ }
+ }
+}).export(module);
ADDED public/teoria-master/test/scales.js
Index: public/teoria-master/test/scales.js
==================================================================
--- public/teoria-master/test/scales.js
+++ public/teoria-master/test/scales.js
@@ -0,0 +1,124 @@
+var vows = require('vows'),
+ assert = require('assert'),
+ teoria = require('../');
+
+vows.describe('Scales').addBatch({
+ 'Ab2': {
+ topic: function() {
+ return teoria.note('Ab2');
+ },
+
+ 'Blues': function(note) {
+ assert.deepEqual(teoria.note('g#').scale('blues').simple(),
+ ['g#', 'b', 'c#', 'd', 'd#', 'f#']);
+ },
+
+ 'Ionian/Major': function(note) {
+ assert.deepEqual(note.scale('ionian').simple(),
+ ['ab', 'bb', 'c', 'db', 'eb', 'f', 'g']);
+ },
+
+ 'Dorian': function(note) {
+ assert.deepEqual(note.scale('dorian').simple(),
+ ['ab', 'bb', 'cb', 'db', 'eb', 'f', 'gb']);
+ },
+
+ 'Phrygian': function(note) {
+ assert.deepEqual(note.scale('phrygian').simple(),
+ ["ab", "bbb", "cb", "db", "eb", "fb", "gb"]);
+ },
+
+ 'Lydian': function(note) {
+ assert.deepEqual(note.scale('lydian').simple(),
+ ["ab", "bb", "c", "d", "eb", "f", "g"]);
+ },
+
+ 'Mixolydian': function(note) {
+ assert.deepEqual(note.scale('mixolydian').simple(),
+ ["ab", "bb", "c", "db", "eb", "f", "gb"]);
+ },
+
+ 'Aeolian/Minor': function(note) {
+ assert.deepEqual(note.scale('aeolian').simple(),
+ ["ab", "bb", "cb", "db", "eb", "fb", "gb"]);
+ },
+
+ 'Locrian': function(note) {
+ assert.deepEqual(note.scale('locrian').simple(),
+ ["ab", "bbb", "cb", "db", "ebb", "fb", "gb"]);
+ },
+
+ 'Major Pentatonic': function(note) {
+ assert.deepEqual(note.scale('majorpentatonic').simple(),
+ ["ab", "bb", "c", "eb", "f"]);
+ },
+
+ 'Minor Pentatonic': function(note) {
+ assert.deepEqual(note.scale('minorpentatonic').simple(),
+ ["ab", "cb", "db", "eb", "gb"]);
+ },
+
+ 'Chromatic': function(note) {
+ assert.deepEqual(note.scale('chromatic').simple(),
+ ["ab", "bbb", "bb", "cb", "c", "db",
+ "d", "eb", "fb", "f", "gb", "g"]);
+ },
+
+ 'Whole Tone': function(note) {
+ assert.deepEqual(teoria.note('c').scale('wholetone').simple(),
+ ["c", "d", "e", "f#", "g#", "a#"]);
+ }
+ },
+
+ 'Is the #get() method octave-relative (pentatonic)?': {
+ topic: function(){
+ return teoria.note('Bb3').scale('majorpentatonic');
+ },
+
+ 'Gets notes w/in octave': function(topic){
+ assert.deepEqual(topic.get(3), teoria.note('D4'));
+ },
+
+ 'Gets notes above octave': function(topic){
+ assert.deepEqual(topic.get(12), teoria.note('C6'));
+ },
+
+ 'Gets notes below octave': function(topic){
+ assert.deepEqual(topic.get(-12), teoria.note('D1'));
+ },
+ },
+
+ 'Is the #get() method octave-relative (diatonic)': {
+ topic: function() {
+ return teoria.note('A4').scale('major');
+ },
+
+ '0 is one note down': function(topic) {
+ assert.deepEqual(topic.get(0), teoria.note('G#4'));
+ },
+
+ '7 is one seventh up': function(topic) {
+ assert.deepEqual(topic.get(7), teoria.note('G#5'));
+ },
+
+ '8 is one octave up': function(topic) {
+ assert.deepEqual(topic.get(8), teoria.note('A5'));
+ },
+
+ '9 is one ninth up': function(topic) {
+ assert.deepEqual(topic.get(9), teoria.note('B5'));
+ },
+
+ '-5 is one seventh down': function(topic) {
+ assert.deepEqual(topic.get(-5), teoria.note('B3'));
+ },
+
+ '-6 is one octave down': function(topic) {
+ assert.deepEqual(topic.get(-6), teoria.note('A3'));
+ },
+
+ '-13 is two octaves down': function(topic) {
+ assert.deepEqual(topic.get(-13), teoria.note('A2'));
+ }
+ }
+}).export(module);
ADDED public/teoria-master/test/solfege.js
Index: public/teoria-master/test/solfege.js
==================================================================
--- public/teoria-master/test/solfege.js
+++ public/teoria-master/test/solfege.js
@@ -0,0 +1,64 @@
+var vows = require('vows'),
+ assert = require('assert'),
+ teoria = require('../');
+
+vows.describe('Solfege').addBatch({
+ 'C in C minor': function() {
+ var note = teoria.note('c');
+ assert.equal(note.solfege(teoria.scale(note, 'minor')), 'do');
+ },
+
+ 'A in d major': function() {
+ var note = teoria.note('a');
+ var tonic = teoria.note('d');
+ assert.equal(note.solfege(teoria.scale(tonic, 'major')), 'so');
+ },
+
+ 'F# in B major': function() {
+ var note = teoria.note('f#');
+ var tonic = teoria.note('B');
+ assert.equal(note.solfege(teoria.scale(tonic, 'major')), 'so');
+ },
+
+ 'C4 in C4 minor': function() {
+ var note = teoria.note('c4');
+ var scale = teoria.scale(note, 'minor');
+ assert.equal(note.solfege(scale, true), 'do');
+ },
+
+ 'A3 in D4 major': function() {
+ var note = teoria.note('a3');
+ var scale = teoria.scale('d4', 'major');
+ assert.equal(note.solfege(scale, true), 'so,');
+ },
+
+ 'F#6 in B5 major': function() {
+ var note = teoria.note('f#6');
+ var scale = teoria.scale('b5', 'major');
+ assert.equal(note.solfege(scale, true), 'so');
+ },
+
+ 'F2 in E6 phrygian': function() {
+ var note = teoria.note('f2');
+ var scale = teoria.scale('e6', 'phrygian');
+ assert.equal(note.solfege(scale, true), 'ra,,,,');
+ },
+
+ 'Eb10 in E8 dorian': function() {
+ var note = teoria.note('eb10');
+ var scale = teoria.scale('e8', 'dorian');
+ assert.equal(note.solfege(scale, true), 'de\'\'');
+ },
+
+ 'A#6 in Bb4 locrian': function() {
+ var note = teoria.note('A#6');
+ var scale = teoria.scale('Bb4', 'locrian');
+ assert.equal(note.solfege(scale, true), 'tai\'');
+ },
+
+ 'E2 in C3 major': function() {
+ var note = teoria.note('e2');
+ var scale = teoria.scale('c3', 'major');
+ assert.equal(note.solfege(scale, true), 'mi,');
+ }
+}).export(module);
ADDED public/tune.js
Index: public/tune.js
==================================================================
--- public/tune.js
+++ public/tune.js
cannot compute difference between binary files
ADDED requirements.txt
Index: requirements.txt
==================================================================
--- requirements.txt
+++ requirements.txt
@@ -0,0 +1,95 @@
+aiofiles==0.6.0
+alabaster==0.7.12
+appdirs==1.4.4
+appnope==0.1.2
+astor==0.8.1
+astroid==2.5.6
+asttokens==2.0.5
+attrs==21.2.0
+Babel==2.9.1
+backcall==0.2.0
+beautifulsoup4==4.9.3
+black==21.5b1
+certifi==2020.12.5
+chardet==4.0.0
+click==7.1.2
+cogapp==3.0.0
+colorama==0.4.4
+decorator==5.0.7
+dialite==0.5.3
+docutils==0.16
+flexx==0.8.1
+funcparserlib==0.3.6
+furo==2021.4.11b34
+httptools==0.2.0
+hy==0.20.0
+idna==2.10
+imagesize==1.2.0
+ipdb==0.13.7
+ipython==7.22.0
+ipython-genutils==0.2.0
+isort==5.8.0
+jedi==0.18.0
+Jinja2==2.11.3
+jsonschema==3.2.0
+jupyter-core==4.7.1
+lazy-object-proxy==1.6.0
+leo==6.3
+Mako==1.1.4
+markdown-it-py==1.1.0
+MarkupSafe==1.1.1
+mccabe==0.6.1
+mdit-py-plugins==0.2.8
+meta==1.0.2
+multidict==5.1.0
+mypy-extensions==0.4.3
+myst-parser==0.14.0
+nbformat==5.1.3
+packaging==20.9
+parso==0.8.2
+pathspec==0.8.1
+pexpect==4.8.0
+pickleshare==0.7.5
+prompt-toolkit==3.0.18
+pscript==0.7.5
+ptyprocess==0.7.0
+pydata-sphinx-theme==0.6.3
+pyflakes==2.3.1
+Pygments==2.8.1
+pylint==2.8.2
+pyparsing==2.4.7
+PyQt5==5.15.4
+PyQt5-Qt5==5.15.2
+PyQt5-sip==12.8.1
+PyQtWebEngine==5.15.4
+PyQtWebEngine-Qt5==5.15.2
+pyrsistent==0.17.3
+pyshortcuts==1.8.0
+pytz==2021.1
+PyYAML==5.4.1
+regex==2021.4.4
+requests==2.25.1
+rply==0.7.8
+sanic==21.3.4
+sanic-routing==0.6.2
+six==1.16.0
+snowballstemmer==2.1.0
+soupsieve==2.2.1
+Sphinx==3.5.4
+sphinx-book-theme==0.1.1
+sphinxcontrib-applehelp==1.0.2
+sphinxcontrib-devhelp==1.0.2
+sphinxcontrib-htmlhelp==1.0.3
+sphinxcontrib-jsmath==1.0.1
+sphinxcontrib-qthelp==1.0.3
+sphinxcontrib-serializinghtml==1.1.4
+toml==0.10.2
+tornado==6.1
+traitlets==5.0.5
+ujson==4.0.2
+urllib3==1.26.4
+uvloop==0.15.2
+wcwidth==0.2.5
+webruntime==0.5.8
+websockets==8.1
+wrapt==1.12.1
ADDED source/Makefile
Index: source/Makefile
==================================================================
--- source/Makefile
+++ source/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
ADDED source/_static/analytics.js
Index: source/_static/analytics.js
==================================================================
--- source/_static/analytics.js
+++ source/_static/analytics.js
@@ -0,0 +1,3 @@
+window.goatcounter = {
+ path: function(p) { return location.host + p }
+}
ADDED source/_static/custom.css
Index: source/_static/custom.css
==================================================================
--- source/_static/custom.css
+++ source/_static/custom.css
@@ -0,0 +1,3 @@
+.custom-nav-footer {
+ margin-top: 8em;
+}
ADDED source/bookmarks.md
Index: source/bookmarks.md
==================================================================
--- source/bookmarks.md
+++ source/bookmarks.md
@@ -0,0 +1,52 @@
+# Bookmarks
+
+## WebAudio
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Obsevers
+
+
+
+
+
+
+
+
+
+
+## Visuals
+
+
+
+
+The code editor borrows some CSS styles from this project
+
+
+## Inspiration
+
+
+
+
+
+
+## Misc
+
+
+
+
+
+
+
+
+
ADDED source/changelog.md
Index: source/changelog.md
==================================================================
--- source/changelog.md
+++ source/changelog.md
@@ -0,0 +1,37 @@
+# Changelog
+
+June - July 2019 ~ First prototype
+
+ Eval, tone.js and an checkoxbased input visualization for rhythm
+
+Feb ~ May 2021 ~ Second Prototype
+
+```
+ Save feature
+ Added Samples
+ Moving to riot javascript
+ Fix for not mounting
+ Refactoring codebase
+ Added sphinx
+ Added number and monitoring for knob
+ Added clock
+ Changed Theme
+ Visuals
+ Observers and Misc Libraries
+ New sections
+ Fix for crackling
+ Docs
+```
+
+June - July 2021 - Demo
+
+```
+ Theme changes
+ Lots of Docs
+ Tutorials
+ Working visuals
+ Code Mirror Issue
+```
+
+
+
ADDED source/common.py
Index: source/common.py
==================================================================
--- source/common.py
+++ source/common.py
@@ -0,0 +1,27 @@
+external_libraries = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+
ADDED source/conf.py
Index: source/conf.py
==================================================================
--- source/conf.py
+++ source/conf.py
@@ -0,0 +1,79 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+
+# -- Project information -----------------------------------------------------
+
+project = 'Bitrhythm'
+copyright = '(c) Xyzzy Apps, 2021'
+author = 'Xyzzy Apps'
+master_doc = "index"
+
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = ['myst_parser', 'sphinx.ext.todo']
+todo_include_todos = True
+
+source_suffix = ['.rst', '.md']
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'sphinx_book_theme'
+html_title = "Bitrhythm's literate documentation"
+
+html_theme_options = {
+ "use_fullscreen_button": True,
+ "single_page": False,
+ "use_download_button": False,
+ "home_page_in_toc": False,
+ "extra_navbar": """
+Bitrhythm App Home
+Xyzzy Apps Home
+
+"""
+}
+
+html_sidebars = {
+ "**": ["sidebar-logo.html", "sbt-sidebar-nav.html", "sbt-sidebar-footer.html"]
+}
+
+html_show_sphinx = False
+html_show_sourcelink = False
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+html_js_files = [
+ 'analytics.js',
+ ('https://analytics.xyzzyapps.link/count.js', {'data-goatcounter': "https://analytics.xyzzyapps.link/count", 'async': "async"}),
+ ]
+html_css_files = ["custom.css"]
ADDED source/demo.md
Index: source/demo.md
==================================================================
--- source/demo.md
+++ source/demo.md
@@ -0,0 +1,555 @@
+# Demo and Tutorial
+
+## Video
+
+VIDEO
+
+Postprocessed using Reaper with EQ and Surround effects to add some sparkle.
+Video is recorded with the help of Blackhole and Kap and rendered by Reaper.
+
+Samples taken from Deep Techno and Dub Techno collections from splice. Sadly I can't distribute the song itself as I would also have to distribute the samples with it.
+
+## Demo Song 1 // Techno
+
+Code for the Demo Song. The visualisation was disabled in the Demo as it was causing a huge lag while recording on both windows and mac.
+
+```
+volume_guard1 = guard([-20,15])
+volume_guard2 = guard([-20,15])
+Tone.Master.volume.value = volume_guard1(Math.round(dials[0]["cell"]() * 30) -20);
+ //mem["stab_channel"].volume.value = volume_guard2(-2);
+ //mem["stab_filter"].frequency.value = Math.round(dials[1]["cell"]() * 10000);
+//mem["l_filter"].frequency.value = Math.round(dials[1]["cell"]() * 1000);
+
+
+scene1 = [
+ cellx("p 1000 1000 1000 1000"),
+ cellx("p x000 0000 0000 0000"),
+ cellx("p 0x00 0000 0x00 x000"),
+ cellx("p 0000 x000 0000 x000"),
+ cellx("p x000 00x0 0x00 x0xx"),
+ cellx("p 00x0 00x0 0000 00x0"),
+ cellx("p 00x0 00x0 00x0 00x0"),
+]
+
+patterns = scene1
+
+always();
+
+function Sample(name, no, filter, volume) {
+ name = name
+ filter = filter || 10000
+ volume = volume || 0
+ mem[name + "_filter"] = new Tone.Filter(filter, 'lowpass', -96);
+ mem[name + "_channel"] = new Tone.Channel({channelCount: 2, volume: volume}).chain(mem[name + "_filter"], mem.master)
+ samples[no].connect(mem[name + "_channel"]);
+}
+
+function p(s, note, len) {
+ note = note || "C3"
+ len = len || "16n"
+ samples[s].triggerAttackRelease(note, len, time);
+}
+
+function once () {
+
+ var vis = initWinamp("Cope - The Neverending Explosion of Red Liquid Fire");
+ render_loop = function () {
+ vis.render();
+ }
+
+ Tone.Master.volume.value = -30
+ mem.master = new Tone.Channel({channelCount: 2, volume: -10}).chain(Tone.Destination);
+
+ Sample("k", 0, 20000, 5);
+ Sample("h", 1, 20000, -5);
+ Sample("sn",2, 6000, -3);
+ Sample("c", 3, 1200, -10);
+ Sample("stab", 4, 420, 10);
+ Sample("l", 5, 20000, 8);
+ Sample("o", 6, 20000, -8);
+
+
+ handlers["1"] = function (val) {
+ if (val > 0.5) {
+ mem["start_snare"] = true;
+ }
+ }
+
+ dials[1]["cell"].onChange(function (e) {
+ var val = parseFloat(e["data"].value);
+ handlers["1"](val);
+ })
+
+}
+
+function tweak () {
+ mem.k1 = knob({initial : 0.42, ramp : [0.42, 0.525, 0.8, 0.4, 1, 0.65, 0.75, 1, 0.8], "number": dials[2]["cell"] });
+ always = function () {
+ mem["stab_filter"].frequency.value = mem.k1.move() * 1000;
+ }
+}
+
+function sampleTest () {
+ Sample("l", 4, 10000, -5);
+
+}
+
+if (bars <= 3 ) {
+ transition = once;
+} else {
+ transition = tweak;
+}
+
+if (isHit) {
+ if (track_no == 1) {
+ if (bars > 4 ) {
+ p(0);
+ }
+ }
+ if (track_no == 2) {
+ if (bars > 8 ) {
+ p(1, "C3", "1n");
+ }
+ }
+ if (track_no == 3) {
+ if (bars > 0 ) {
+ p(4, "C3", "1n");
+ }
+ if (bars == 15) {
+ transition();
+ }
+ }
+ if (track_no == 4) {
+ if( mem["start_snare"]) {
+ p(2);
+ }
+
+ }
+ if (track_no == 5) {
+ }
+ if (track_no == 6) {
+ if (bars > 12) {
+ p(5, "F2", "16n")
+ }
+ }
+ if (track_no == 7) {
+ if (bars > 48) {
+
+ p(6, "C3", "48n")
+ }
+ }
+
+}
+```
+
+
+## Example1
+
+This illustrates the core concepts of bitrhythm.
+
+1. Samples (Tone.Sampler)
+2. Dials (use cellx internally)
+3. Observers (cellx)
+
+See for more notes.
+
+For an understanding of the global variables see the concepts and code walkthrough section.
+
+ - patterns and track_no
+ - isHit, current_bit
+ - samples
+ - Tone
+ - cellx
+ - window and any thing included with the script tag is available here
+
+`mem` is short for memory. All instruments and effects are saved here so that they can be accessed everywhere.
+
+Step to create the basic song.
+
+1. Click on `Add Sample URL` to add the following URLs
+
+ - /Kick01.wav
+ - /Snare19.wav
+ - /Closedhat01.wav
+ - /MiscSynthStab04.wav
+
+2. Click on `Add Dial`
+
+3. Enter the following into the window
+
+```
+scene1 = [
+ cellx("p 1000 1000 1000 1000"),
+ cellx("p 00x0 00x0 00x0 00x0"),
+ cellx("p 0000 x000 0000 x000"),
+]
+
+scene2 = [
+ cellx("p 1011 1001 1000 1000"),
+ cellx("p 00x0 00x0 00x0 00x0"),
+ cellx("p 0000 x000 0000 x000"),
+]
+
+patterns = scene1
+
+function Sample(name, no) {
+ name = name || "sample"
+ name += no
+ mem[name + "_filter"] = new Tone.Filter(10000, 'lowpass', -96);
+ mem[name + "_channel"] = new Tone.Channel({channelCount: 2, volume:0}).chain(mem[name + "_filter"], mem.master)
+ samples[no].connect(mem[name + "_channel"]);
+}
+
+function p(s, note) {
+ note = note || "C3"
+ samples[s].triggerAttack(note, time);
+}
+
+var once = function () {
+ Tone.Master.volume.value = -30
+ mem.master = new Tone.Channel({channelCount: 2, volume: -10}).chain( Tone.Destination);
+ mem.volume_guard = guard([-20,-10]);
+
+ Sample("k", 0);
+ Sample("h", 1);
+ Sample("sn", 2);
+
+ handlers["ex"] = function (val) {
+ if (val > 0.5) {
+ mem["start_snare"] = true;
+ }
+ }
+
+ dials[0]["cell"].onChange(function (e) {
+ var val = parseFloat(e["data"].value);
+ handlers["ex"](val);
+ })
+
+}
+
+if (bars <= 3 ) {
+ transition = once;
+} else {
+ transition = tweak;
+}
+
+if (isHit) {
+ if (track_no == 1) {
+ p(0);
+ }
+ if (track_no == 2) {
+ p(1);
+ }
+ if (track_no == 3) {
+ if (mem["start_snare"]) {
+ p(2);
+ };
+ }
+}
+
+
+```
+
+Now try changing the code.
+
+```
+patterns = scene2
+```
+
+Increase the dial to see the addition of the snare. This is how you can use observers to trigger unrelated changes. I call them sideevents, as the logic is similar to sidechain, which typically observers the volume.
+
+Comment and Uncomment lines in the `if (isHit)` block. To mute and unmute sections.
+
+*Note:* `mem["k0_channel"].solo = true;` is not working.
+
+**Visuals**
+
+Change the once function to this and click `+ Execute Once`
+
+Code is taken from butterchurn. Try changing [presets](https://butterchurnviz.com/) to get different visuals.
+
+```
+var tweak = function() {
+ var can = document.getElementById("visual");
+ var can_container = document.getElementById("canvas-container");
+ can.width = window.innerWidth;
+ can.height = window.innerHeight;
+ can_container.width = window.innerWidth;
+ window.visualizer = window.butterchurn.default.createVisualizer(Tone.getContext().rawContext, can, {
+ width: window.innerWidth,
+ height: window.innerHeight,
+ });
+ window.visualizer.connectAudio(Tone.getContext().destination);
+ const presets = butterchurnPresets.getPresets();
+ const preset = presets["_Aderrasi - Wanderer in Curved Space - mash0000 - faclempt kibitzing meshuggana schmaltz (Geiss color mix)"]
+ window.visualizer.loadPreset(preset, 0.0); // 2nd argument is the number of seconds to blend presets
+
+ render_loop = function () {
+ window.visualizer.render();
+ }
+}
+```
+
+**Tweaking**
+
+First click `+ Number`. This is useful to check if the knob function is actually working. And click `+ Execute Once`
+
+```
+var tweak = function () {
+ mem.k1 = knob({ramp : [0.09,1.8, 0.4, 2, 1.5, 1, 0.5, 3, 5, 8, 2], "number": numbers[0]["v"] });
+ always = function () {
+ mem["k0_filter"].frequency.value = mem.k1.move() * 1000;
+ }
+}
+```
+
+As you can sere numbers and dials will be available as a global array.
+
+There is no way to remove them so be careful about the order in which you add them.
+
+The following code will always be executed as its at the top level. As you can see this code implies that the first dial is connected to the master volume. Use guards to avoid going deaf as sometimes editing can created bad frequency numbers.
+
+```
+Tone.Master.volume.value = volume_guard((1 - dials[0]["cell"]()) * -30);
+```
+
+## Example2
+
+- Kick + Filter
+- Snare + Filter
+- Snare + Filter + Delay
+- High Hat
+- Lead + Filter
+- Dub Stab + Filter + Reverb
+
+Tip: In Tone.js you can't call connect one after another, you need to use chain.
+
+TODO: Add glide to Lead to make it more 303 sounding
+
+Master is connected with Surround and Volume Limiter.
+Use Gates and Limiters to avoid going deaf.
+
+Tone.MultiInstrument gave lots of glitches, so custom voices are written in the voice function
+
+Channels provide
+- Mute
+- Solo
+
+More improvements for the Stab
+
+ - Chorus or Phaser
+ - Compressor
+ - Decay in envelope
+ - Separate filters
+ - EQ
+ - Sends for more delay
+ - LFO for filters
+
+
+Freeverb does not work and also needs Mono to function properly
+
+```
+var reverb = new Tone.Freeverb().toDestination();
+var reverb_mono = new Tone.Mono().connect(reverb);
+reverb.dampening = 100;
+reverb.roomSize = 0.9;
+```
+
+## Full Code
+
+```
+volume_guard1 = guard([-20,15])
+volume_guard2 = guard([-20,15])
+Tone.Master.volume.value = volume_guard1(Math.round(dials[0]["cell"]() * 30) -20);
+ //mem["stab_channel"].volume.value = volume_guard2(-2);
+// mem["stab_filter"].frequency.value = Math.round(dials[1]["cell"]() * 10000);
+// mem["l_filter"].frequency.value = Math.round(dials[1]["cell"]() * 1000);
+
+
+scene1 = [
+ cellx("p 1000 1000 1000 1000"),
+ cellx("p 00x0 00x0 00x0 00x0"),
+ cellx("p 0x00 0000 0000 x000"),
+ cellx("p 0000 x000 0000 x000"),
+ cellx("p xx0x x0x0 x0x0 0xxx"),
+ cellx("p x000 x0x0 0000 x0x0"),
+]
+
+patterns = scene1
+
+always();
+
+function NoiseSynth (name) {
+ name = name || "wf";
+ mem[name + "_stereo"] = new Tone.StereoWidener({width: 1});
+ mem[name] = new Tone.Noise("pink").start();
+ mem[name + "_filter"] = new Tone.Filter(400, 'lowpass', -96);
+ mem[name + "_channel"] = new Tone.Channel({channelCount: 2, volume: -10, pan: -0.8}).chain(mem[name + "_filter"], mem[name + "_stereo"], mem.master);
+ mem[name].connect(mem[name + "_channel"])
+}
+
+
+function Sample(name, no, filter, volume) {
+ name = name
+ filter = filter || 10000
+ volume = volume || 0
+ mem[name + "_filter"] = new Tone.Filter(filter, 'lowpass', -96);
+ mem[name + "_channel"] = new Tone.Channel({channelCount: 2, volume: volume}).chain(mem[name + "_filter"], mem.master)
+ samples[no].connect(mem[name + "_channel"]);
+}
+
+function Stab(name) {
+ name = name || "stab";
+
+ mem[name + "_filter"] = new Tone.Filter(5250, 'lowpass', -96);
+ mem[name + "_hfilter"] = new Tone.Filter(80, 'highpass', -96);
+ mem[name + "_reverb"] = new Tone.Reverb(0.1);
+ mem[name + "_delay"] = new Tone.FeedbackDelay("4n", 0.4);
+ // mem[name + "_pdelay"] = new Tone.PingPongDelay("2n", 0.1);
+ mem[name + "_stereo"] = new Tone.StereoWidener({width: 0.25});
+ mem[name + "_channel"] = new Tone.Channel({channelCount: 2, volume: -2}).chain(mem[name + "_filter"] , mem[name + "_delay"], mem[name + "_reverb"], mem[name + "_hfilter"] ,mem[name + "_stereo"], mem.master)
+
+
+ function voice(no, type) {
+ mem[name + "_synth" + no] = new Tone.MonoSynth({
+ oscillator: {
+ type: type
+ }
+ })
+ mem[name + "_synth" + no].connect(mem[name + "_channel"]);
+ }
+
+ voice(1, "sawtooth")
+ voice(2, "sawtooth")
+ voice(3, "sawtooth")
+ voice(4, "pwm")
+ voice(5, "pwm")
+ voice(6, "pwm")
+}
+
+
+function p(s, note, len) {
+ note = note || "C3"
+ len = len || "16n"
+ samples[s].triggerAttackRelease(note, len, time);
+}
+
+
+function s(vel, notes, duration) {
+ vel = vel || 1.0;
+ duration = duration || "2n";
+ notes = notes || ["E2", "B2", "G2"];
+ mem["stab_synth1"].triggerAttackRelease(notes[0], duration, time, vel);
+ mem["stab_synth2"].triggerAttackRelease(notes[1], duration, time, vel);
+ mem["stab_synth3"].triggerAttackRelease(notes[2], duration, time, vel);
+
+ mem["stab_synth4"].triggerAttackRelease(notes[0], duration, time, vel);
+ mem["stab_synth5"].triggerAttackRelease(notes[1], duration, time, vel);
+ mem["stab_synth6"].triggerAttackRelease(notes[2], duration, time, vel);
+}
+
+
+
+
+function once () {
+
+ // var vis = initWinamp("Unchained - Rewop");
+ render_loop = function () {
+ // vis.render();
+ }
+
+ Tone.Master.volume.value = -30
+ mem.master = new Tone.Channel({channelCount: 2, volume: -10}).chain(Tone.Destination);
+
+ // NoiseSynth();
+ Stab();
+ Sample("k", 0, 3000, 3);
+ Sample("h", 1, 7000, -15);
+ Sample("sn",2, 6000, -15);
+ Sample("c", 3, 800, -10);
+ Sample("l", 4, 420, -15);
+
+
+ handlers["1"] = function (val) {
+ if (val > 0.5) {
+ mem["start_snare"] = true;
+ } else {
+ mem["start_snare"] = false;
+ }
+ mem["stab_filter"].Q.value = Math.round(val * 5);
+ }
+
+ dials[1]["cell"].onChange(function (e) {
+ var val = parseFloat(e["data"].value);
+ handlers["1"](val);
+ })
+
+
+
+}
+
+
+function tweak () {
+ mem.k1 = knob({ramp : [0.525, 0.8, 0.4, 1, 0.25, 0.75, 1, 0.25, 0.1], "number": dials[2]["cell"] });
+ always = function () {
+ mem["stab_filter"].frequency.value = mem.k1.move() * 10000;
+ }
+}
+
+
+
+
+function sampleTest () {
+ Sample("l", 4, 10000, -5);
+
+}
+
+
+
+if (bars <= 3 ) {
+ transition = once;
+} else {
+ transition = tweak;
+}
+
+if (isHit) {
+ if (track_no == 1) {
+ if (bars > 0 ) {
+ p(0);
+ }
+ }
+ if (track_no == 2) {
+ if (bars > 8 ) {
+ p(1);
+ }
+ }
+ if (track_no == 3) {
+ if (bars > 4 ) {
+ s();
+ }
+ if (bars == 6) {
+ transition();
+ }
+ }
+ if (track_no == 4) {
+ // Uncomment for snare
+ // if (mem["start_snare"]) {
+ p(2);
+ // }
+
+ }
+ if (track_no == 5) {
+ if (bars > 12) {
+ p(3)
+ }
+ // a();
+ }
+ if (track_no == 6) {
+ // p(4, "B3", "8n")
+ }
+
+}
+```
+
+
ADDED source/early-attempts.md
Index: source/early-attempts.md
==================================================================
--- source/early-attempts.md
+++ source/early-attempts.md
@@ -0,0 +1,88 @@
+# Early Attempts
+
+My first attempt at this project was called bang256 ~ 2016, meant to mess with Renoise's OSC. Its still usable.
+
+The music loop for OSC sequencing. You need to install osc and nanotimer. Alternatively you can use portmidi
+
+```
+var fs = require("fs");
+var osc = require("osc");
+var NanoTimer = require('nanotimer');
+var timer = new NanoTimer();
+
+function bang256(host, port, tempo, lines_per_beat, measure, tracks, title) {
+
+ var udpPort = new osc.UDPPort({
+ remoteAddress: host,
+ remotePort: port
+ });
+
+ udpPort.open();
+
+ var beat = 60.0 / tempo;
+ var lines_per_measure = lines_per_beat * measure;
+ var tick = beat / lines_per_beat;
+ var bangs = 0;
+ var count = 0;
+
+ timer.setInterval(function() {
+ var bang = (bangs % lines_per_measure);
+ if (bang == 0) {
+ count++;
+ }
+ for (var t = 1; t <= tracks; t++) {
+ var path = title + "/" + t;
+ if (fs.existsSync(path)) {
+ var code = fs.readFileSync(path, {encoding: 'utf-8'});
+ var f = eval(code);
+ f(udpPort, bang, count, bangs);
+ }
+ }
+
+ bangs++;
+
+ }, '', tick + "s");
+
+}
+
+var argv = require('minimist')(process.argv.slice(2));
+var bang256 = require("bang256").bang256;
+
+var host = argv["host"] || "127.0.0.1";
+var port = argv["port"] || 8000;
+var tempo = argv["tempo"];
+var lines_per_beat = argv["lpb"] || 4;
+var measure = argv["measure"] || 4;
+var tracks = argv["tracks"] || 4;
+var title = argv["title"];
+
+bang256(host, port, tempo, lines_per_beat, measure, tracks, title);
+```
+
+To run,
+
+```
+node ./bang256 -host "127.0.0.1" --tempo=120 --lpb=4 --measure=4 --tracks=4 --title=foo
+```
+
+Edit files in foo/1 and foo/2 and foo/3 and foo/4 to create live music with an OSC compatible synthesizer.
+
+Inside the file `foo/1`
+
+```
+(function (osc, bang, count, bangs, thread) {
+
+ if ([4, 12].indexOf(bang) != -1) {
+ var msg = {
+ address: "/renoise/trigger/note_on",
+ args: [ 1, 1, 38, 127],
+ };
+ osc.send(msg);
+ }
+
+})
+```
+
+ also uses the ticks approach but uses a message bus for events.
+
+
ADDED source/index.rst
Index: source/index.rst
==================================================================
--- source/index.rst
+++ source/index.rst
@@ -0,0 +1,27 @@
+Welcome to Bitrhythm's documentation!
+===========================================
+
+
+.. meta::
+ :description lang=en: Bitrhythm's - literate documentation
+ :keywords: literate programming, p5, live coding, algorave, demoscene, creative programming, music, techno, programming, webaudio, webgl, improvising
+
+.. toctree::
+ :caption: Table of Contents
+ :maxdepth: 5
+ :glob:
+ :includehidden:
+
+ what
+ source-code
+ demo
+ tweaking
+ limitations
+ midi
+ samples
+ early-attempts
+ changelog
+ main
+ saving
+ bookmarks
+
ADDED source/limitations.md
Index: source/limitations.md
==================================================================
--- source/limitations.md
+++ source/limitations.md
@@ -0,0 +1,10 @@
+# Limitations and Recording
+
+- Opening developer tools might slow down the execution a bit
+- Using excessive delays and reverbs seems to cause some glitches
+- Mobile browsers don't work properly
+- Firefox does not seem to work well with tone.js
+
+I use [BlackHole](https://existential.audio/blackhole/) to record audio from the browser window to Reaper. You can use any screenrecording software to record the screen. Have a distinct sound to match both audio and video.
+
+
ADDED source/main.cog
Index: source/main.cog
==================================================================
--- source/main.cog
+++ source/main.cog
@@ -0,0 +1,1386 @@
+@<
+@>
+This is just a sample
+Copyright (C) 2021 Xyzzy Apps
+See https://bitrhythm.xyzzyapps.link/docs/source-code.html for the latest source code
+@@
+
+# Concepts and Code Walkthrough
+
+@<
+import cog
+import os
+
+if DEV == "1":
+ stuff = """
+## Running
+
+ls source/main.cog | entr -r runserver.sh -b
+"""
+ cog.out(stuff)
+@>
+@@
+
+## Core Tracker Loop
+
+In bitrhythm code is evaluated for every cycle.
+
+1 beat = 60 / tempo
+1 cycle = 1 beat / ticks
+
+For every cycle visual and audio code is evaluated.
+
+The edit checkbox allows you to perform long edits, where only old code is evaluated. Once you disable it, all the new edit changes are applied in the next cycle.
+
+If there is any syntax error, previous working code is used.
+
+If the click the `execute transition` is selected, the transition function is run. Use this progressing the song from initializing to tweaking.
+
+
+Patterns is an array of strings, each string can be hexadecimal, decimal or something like “x000 x000 x000 x000”.
+isHit and track_no can be used to identify the layer in the live editor. Hexadecimal uses \`0 \`1 \`2 \`3 \`4 \`5 instead of the Roman numerals abcde for 10, 11, 12 ...
+
+Scheduled Time as signified by the variable time is crucial when calling note triggers. This is used by Tone.js to schedule notes to play in the future.
+
+### Observers
+
+Sidechain compression is a simple algorithm which observes amplitude of another instrument but you can generalise it to anything. By attaching observers to time or other instruments you can create sections within the song that can trigger others with conditional logic. This is similar to pure data's bangs - [see this](https://www.youtube.com/watch?v=nTTZZyD4xlE). In future this will be referred to as side events. You could decrease the volume of the drums to have the snares drop automatically for example.
+
+This is something that you can't do in DAWs.
+
+```{code-block} js
+---
+force: true
+---
+
+@<
+core_loop = """
+
+async play() {
+ var self = this;
+ var cellx = window.cellx.cellx;
+
+ await Tone.start();
+ Tone.Transport.start();
+ Tone.Transport.bpm.value = this.state.tempo;
+ Tone.Transport.swing.value = 0;
+
+ var transition = function () {
+ }
+
+ var always = function () {
+ }
+
+
+ var render_loop = function () {
+ }
+
+ var animation = function () {
+ render_loop();
+ window.requestAnimationFrame(animation)
+ }
+
+ Tone.Master.mute = false;
+ document.getElementById('tempo-value').disabled = true;
+ document.getElementById('tick-value').disabled = true;
+
+ var mem = self.state.mem;
+ var handlers = {};
+ var count = -1;
+
+ var text = editor.getValue();
+ editor.on("change", function () {
+ text = editor.getValue();
+ });
+
+ var patterns = [ cellx("0000") ]; // need this for first eval
+
+ var bars = 0;
+ var tick = 0;
+
+ loop = new Tone.ToneEvent((time, chord) => {
+ count = count + 1;
+ tick = (count % this.state.ticks);
+ if (tick === 0) ++bars;
+
+ $("#duration").html("" + bars + "." + tick + " / " + count + " / " + window.roundTo(Tone.Transport.seconds, 2));
+
+ for (var i = 0; i < patterns.length; i++) {
+ var samples = this.state.samples;
+ var dials = self.state.dials;
+ var numbers = self.state.numbers;
+
+ if (document.getElementById('edit-mode').checked) {
+ p = oldPatterns[i];
+ } else {
+ var p = patterns[i];
+ oldPatterns[i] = p;
+ }
+ if (p && p.length !== 0) {
+ var track_no = i + 1;
+ var pattern = pattern_clean(p());
+ var isHit = (pattern.split('')[tick] == "1") ? true : false;
+
+ try {
+ if (document.getElementById('edit-mode').checked) {
+ eval(oldCode);
+ } else {
+ eval(text);
+ if (document.getElementById('load-mode').checked) {
+ document.getElementById('load-mode').checked = false;
+ transition();
+ }
+ oldCode = text;
+ }
+ $("#error").html("");
+ } catch (ex) {
+ $("#error").html(ex);
+ eval(oldCode);
+ }
+ }
+ }
+ }, []);
+ loop.loop = true;
+ loop.loopEnd = this.state.ticks + "n";
+ loop.start();
+
+ window.requestAnimationFrame(animation)
+
+}
+"""
+cog.out(core_loop)
+@>
+@@
+```
+
+## Dials
+
+Bitrhythm provides custom dials. These dials can be mapped to any aspects of Tone.js. All dials are available as an array dials in the live code editor.
+
+```{code-block} html
+---
+force: true
+---
+
+@<
+import cog
+import os
+
+dial = """
+
+
+
+
+
+
+
+
+"""
+
+cog.out(dial)
+os.system("rm public/components/dial.tag")
+f = open("public/components/dial.tag", "w")
+f.write(dial)
+f.close()
+@>
+@@
+```
+
+## Numbers
+
+These numbers can be mapped to any aspect of Tone.js. All number boxes are available as an array numbers in the live code editor. Useful for debugging purposes.
+
+```{code-block} html
+---
+force: true
+---
+
+@<
+import cog
+import os
+
+number = """
+
+
+
+
+
+
+
+
+"""
+
+cog.out(number)
+os.system("rm public/components/number.tag")
+f = open("public/components/number.tag", "w")
+f.write(number)
+f.close()
+@>
+@@
+```
+
+## AutoKnob
+
+AutoKnob enables programmatic automation in Bitrhythm
+
+`x -> [1, 2.5, 4, 3.2] | by 0.3`
+
+x will go from 1 to 2.5 to 4 to 3.2 in increments of 0.3 for every tick. While x will increase till 4 ... it will decrease once it reaches 4 and drop down to 3.2. After reaching 3.2 you can stay there or reverse back. At any point during live editing, you can add an extra element to the array. If you add 5 for example, the loop will continue from 3.2 to 5.
+
+You can think of each element in the array as the "final knob position" and in each cycle we are moving to the next knob position in increments of 0.3
+
+An alternate to AutoKnob is to use TimedKnob. In the endless acid banger project, the basic code was using a simple timer to randomly move the knob position along with note collections and weighted random choice on note collections for generating rhythms.
+
+TimedKnobs can be used to add small variations in volume to make the drums sounds more natural.
+
+```{code-block} js
+---
+force: true
+---
+
+@<
+import cog
+
+knob_code = """
+function knob(options) {
+ options = options || {};
+ var context = {};
+ context.ramp = options.ramp || [0 , 1];
+ context.count_skip = options.speed || 4;
+ context.step = options.step || 0.01;
+ context.reverse = options.reverse || true;
+ context.number = options.number || null;
+
+ context.current_count = 0;
+ context.index = 0;
+ context.val = window.cellx.cellx(options.initial || 0.5)
+
+ function changeContext() {
+ context.next_val = context.ramp[context.index + 1];
+
+ context.val(context.ramp[context.index]);
+ if (context.val() > context.next_val) {
+ context.direction = -1;
+ } else {
+ context.direction = 1;
+ }
+ }
+
+ changeContext();
+
+ return {
+ "cell": context.val,
+ "push": function (val) {
+ context.ramp.push(val);
+ },
+ "replace": function (val) {
+ context.ramp = val;
+ },
+ "speed": function (val) {
+ context.count_skip = val;
+ },
+ "step": function (val) {
+ context.step = val;
+ },
+ "up": function (val) {
+ val = val || 0.1;
+ context.ramp.push(context.ramp[context.ramp.length - 1] + val);
+ },
+ "down": function (val) {
+ val = val || -0.1;
+ context.ramp.push(context.ramp[context.ramp.length - 1] + val);
+ },
+ "move": function () {
+ if (context.current_count >= context.count_skip) {
+ context.current_count = 1;
+
+ if (context.direction == 1) {
+ var cmp = function () {
+ return (context.val() >= context.next_val);
+ };
+ } else {
+ var cmp = function () {
+ return (context.next_val >= context.val());
+ };
+ }
+
+ if (cmp()) {
+ context.val(context.next_val);
+ context.index = context.index + 1;
+ if (context.index === context.ramp.length -1) {
+ if (context.reverse) {
+ context.index = 0;
+ context.ramp = context.ramp.reverse();
+ } else {
+ context.index = context.index - 1;
+ }
+ }
+ changeContext();
+ context.val(context.val() + context.step * context.direction);
+ if (context.number) context.number(context.val());
+ } else {
+ context.val(context.val() + context.step * context.direction);
+ if (context.number) context.number(context.val());
+ }
+ } else {
+ context.current_count += 1;
+ }
+
+ return context.val();
+ }
+ }
+}
+
+function timedKnob(options) {
+ options = options || {};
+ var context = {};
+ context.interval = options.interval || 100;
+ context.knob = knob(options);
+
+ context.timer = setInterval(function () {
+ context.knob.move();
+ }, context.interval);
+
+ context.knob["clear"] = function () {
+ clearInterval(context.timer);
+ }
+
+ return context.knob;
+}
+"""
+cog.out(knob_code)
+@>
+@@
+```
+
+## Main UI
+
+@<
+import cog
+import os
+
+if DEV == "1":
+ stuff = """
+```
+// Not working
+
+.CodeMirror-selected,
+.CodeMirror-focused,
+.CodeMirror-activeline,
+.CodeMirror-activeline-background {
+ background: transparent;
+ color: #882d2d;
+ z-index: 5 !important;
+}
+```
+"""
+ cog.out(stuff)
+@>
+@@
+
+
+```{code-block} html
+---
+force: true
+---
+
+@<
+import cog
+import os
+
+bitrhythm = """
+
+
+
+
+
+
+
+
+
+
+"""
+from mako.template import Template
+
+code = Template(bitrhythm).render(core_loop=core_loop)
+cog.out(bitrhythm)
+os.system("rm public/components/bitrhythm.tag")
+f = open("public/components/bitrhythm.tag", "w")
+f.write(code)
+f.close()
+@>
+@@
+```
+
+## Sample
+
+You can add samples using the file upload. All samples are available as an array – samples. Initialise samples, global variables and synthesisers using the transition function and change the sample parameters using the same during live coding.
+
+```{code-block} html
+---
+force: true
+---
+
+@<
+import cog
+import os
+
+sample = """
+
+
+
+
+
+ { getLast(this.props.ti -1)}
+ (x)
+
+
+
+
+
+
+"""
+
+cog.out(sample)
+os.system("rm public/components/sample.tag")
+f = open("public/components/sample.tag", "w")
+f.write(sample)
+f.close()
+@>
+@@
+```
+
+@<
+import cog
+import os
+
+app = """
+(import [sanic [Sanic response]])
+(import [sanic.response [json text]])
+(import [sanic.exceptions [NotFound abort]])
+(import [jinja2 [Environment FileSystemLoader]])
+(import re)
+(import ipdb)
+(import sys)
+(import traceback)
+(import json)
+(import datetime)
+(import [email.utils [format_datetime]])
+(import [urllib.parse [urlparse]])
+(import base64)
+
+(setv file-loader (FileSystemLoader "templates"))
+(setv env (Environment :loader file-loader))
+
+(setv app (Sanic "Bitrhythm"))
+
+(with-decorator
+ (app.exception NotFound)
+ (defn/a ignore_404s [request exception]
+ (return (text (+ "Yep, I totally found the page " request.url)))
+ )
+)
+
+(with-decorator
+ (app.route "/song/")
+ (defn/a get-index [request name]
+ (setv template (env.get_template "index.html"))
+ (return (response.html (template.render {"data" name})))
+ )
+)
+
+(with-decorator
+ (app.route "/")
+ (defn/a get-index [request]
+ (setv template (env.get_template "index.html"))
+ (return (response.html (template.render {"data" ""})))
+ )
+)
+
+(with-decorator
+ (app.route "/issue")
+ (defn/a get-index [request]
+ (setv template (env.get_template "page.html"))
+ (return (response.html (template.render)))
+ )
+)
+
+(app.static "/" "./public")
+
+(defmain [&rest args]
+ (app.run :host "0.0.0.0" :port 8015)
+)
+"""
+
+if DEV == "1":
+ for_docs = """
+## App
+
+```{code-block} hylang
+---
+force: true
+---
+%s
+```
+"""
+ cog.out(for_docs % (app,))
+os.system("rm bitrhythm.hy")
+f = open("bitrhythm.hy", "w")
+f.write(app)
+f.close()
+@>
+@@
+
+@<
+import cog
+import os
+
+index = """
+
+
+
+
+
+
+ Bitrhythm
+
+
+
+
+
+
+
+
+ ${common_scripts}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+from mako.template import Template
+import common
+code = Template(index).render(common_scripts=common.external_libraries)
+
+if DEV == "1":
+ for_docs = """
+## Index
+
+```{code-block} html
+---
+force: true
+---
+%s
+```
+ """
+ cog.out(for_docs % (index,))
+
+os.system("rm templates/index.html")
+f = open("templates/index.html", "w")
+f.write(code)
+f.close()
+@>
+@@
+
+
+@<
+import cog
+import os
+
+page = """
+
+
+
+
+
+
+
+
+ About birthythm
+
+
+
+
+
+
+
+
+
+
+"""
+
+if DEV == "1":
+ for_docs = """
+## Page
+
+```{code-block} html
+---
+force: true
+---
+%s
+```
+ """
+ cog.out(for_docs % (page,))
+
+os.system("rm templates/page.html")
+f = open("templates/page.html", "w")
+f.write(page)
+f.close()
+@>
+@@
+
+## Javascript
+
+```{code-block} js
+---
+force: true
+---
+
+@<
+import cog
+import os
+
+misc_js = """
+function initWinamp(preset) {
+ var can = document.getElementById("visual");
+ can.height = window.innerHeight - document.getElementById("header-playback").clientHeight - 75;
+ can.width = window.innerWidth;
+ var can_container = document.getElementById("canvas-container");
+ can_container.width = window.innerWidth;
+ var visualizer = window.butterchurn.default.createVisualizer(Tone.getContext().rawContext, can, {
+ height: window.innerHeight - document.getElementById("header-playback").clientHeight - 75,
+ width: window.innerWidth,
+ meshWidth: 24,
+ meshHeight: 18,
+ });
+ visualizer.connectAudio(Tone.getContext().destination);
+ const presets = window.butterchurnPresets.getPresets();
+ const presetParam = presets[preset];
+ visualizer.loadPreset(presetParam, 0.0); // 2nd argument is the number of seconds to blend presets
+ return visualizer;
+}
+
+function guard(range) {
+ var state = null;
+ return function (val) {
+ if ((val >= range[0]) && (val <= range[1])) {
+ state = val;
+ return val;
+ } else {
+ return state;
+ }
+ }
+}
+
+function hex2bin(hex) {
+ var letters = hex.replace('`1','a');
+ letters = hex.replace('`2','b');
+ letters = hex.replace('`3','c');
+ letters = hex.replace('`4','d');
+ letters = hex.replace('`5','e');
+ letters = hex.replace('`6','f');
+ letters = letters.split('');
+ var bin = "";
+ letters.map(function(letter) {
+ if (letter == "0") {
+ bin += "0000";
+ }
+ if (letter == "1") {
+ bin += "0001";
+ }
+ else if (letter == "2") {
+ bin += "0010";
+ }
+ else if (letter == "3") {
+ bin += "0011";
+ }
+ else if (letter == "4") {
+ bin += "0100";
+ }
+ else if (letter == "5") {
+ bin += "0101";
+ }
+ else if (letter == "6") {
+ bin += "0110";
+ }
+ else if (letter == "7") {
+ bin += "0111";
+ }
+ else if (letter == "8") {
+ bin += "1000";
+ }
+ else if (letter == "9") {
+ bin += "1001";
+ }
+ else if (letter == "a") {
+ bin += "1010";
+ }
+ else if (letter == "b") {
+ bin += "1011";
+ }
+ else if (letter == "c") {
+ bin += "1100";
+ }
+ else if (letter == "d") {
+ bin += "1101";
+ }
+ else if (letter == "e") {
+ bin += "1110";
+ }
+ else if (letter == "f") {
+ bin += "1111";
+ }
+
+ })
+ return bin;
+}
+
+
+function pattern_clean(p) {
+ if (!p) {
+ return "";
+ }
+ p = p.replace(/ /g, "");
+ var fc = p.split('')[0];
+ if (fc== "p") {
+ var ptype = "xo";
+ var l = (p.length - 1);
+ }
+ else if (fc== "b") {
+ var l = (p.length - 1);
+ var ptype = "bin";
+ }
+ else {
+ var ptype = "hex";
+ var l = (p.length) * 4;
+ }
+
+ if (ptype == "bin") {
+ var fp = p.substring(1);
+ } else if (ptype == "xo") {
+ var fp = p.substr(1);
+ fp = fp.replace(/x/g, "1");
+ } else {
+ var fp = hex2bin(p);
+ }
+
+ return fp;
+}
+
+function download(data, filename, type) {
+ var file = new Blob([data], { type: type });
+ if (window.navigator.msSaveOrOpenBlob) // IE10+
+ window.navigator.msSaveOrOpenBlob(file, filename);
+ else { // Others
+ var a = document.createElement("a"),
+ url = URL.createObjectURL(file);
+ a.href = url;
+ a.download = filename;
+ document.body.appendChild(a);
+ a.click();
+ setTimeout(function () {
+ document.body.removeChild(a);
+ window.URL.revokeObjectURL(url);
+ }, 0);
+ }
+}
+
+// https://stackoverflow.com/questions/15762768/javascript-math-round-to-two-decimal-places
+function roundTo(n, digits) {
+ var negative = false;
+ if (digits === undefined) {
+ digits = 0;
+ }
+ if (n < 0) {
+ negative = true;
+ n = n * -1;
+ }
+ var multiplicator = Math.pow(10, digits);
+ n = parseFloat((n * multiplicator).toFixed(11));
+ n = (Math.round(n) / multiplicator).toFixed(digits);
+ if (negative) {
+ n = (n * -1).toFixed(digits);
+ }
+ return n;
+}
+
+${knob_code}
+"""
+from mako.template import Template
+
+code = Template(misc_js).render(knob_code=knob_code)
+cog.out(misc_js)
+
+os.system("rm public/misc.js")
+f = open("public/misc.js", "w")
+f.write(code)
+f.close()
+@>
+@@
+```
+
ADDED source/main.md
Index: source/main.md
==================================================================
--- source/main.md
+++ source/main.md
@@ -0,0 +1,1231 @@
+
+# Concepts and Code Walkthrough
+
+
+## Running
+
+ls source/main.cog | entr -r runserver.sh -b
+
+## Core Tracker Loop
+
+In bitrhythm code is evaluated for every cycle.
+
+1 beat = 60 / tempo
+1 cycle = 1 beat / ticks
+
+For every cycle visual and audio code is evaluated.
+
+The edit checkbox allows you to perform long edits, where only old code is evaluated. Once you disable it, all the new edit changes are applied in the next cycle.
+
+If there is any syntax error, previous working code is used.
+
+If the click the `execute transition` is selected, the transition function is run. Use this progressing the song from initializing to tweaking.
+
+
+Patterns is an array of strings, each string can be hexadecimal, decimal or something like “x000 x000 x000 x000”.
+isHit and track_no can be used to identify the layer in the live editor. Hexadecimal uses \`0 \`1 \`2 \`3 \`4 \`5 instead of the Roman numerals abcde for 10, 11, 12 ...
+
+Scheduled Time as signified by the variable time is crucial when calling note triggers. This is used by Tone.js to schedule notes to play in the future.
+
+### Observers
+
+Sidechain compression is a simple algorithm which observes amplitude of another instrument but you can generalise it to anything. By attaching observers to time or other instruments you can create sections within the song that can trigger others with conditional logic. This is similar to pure data's bangs - [see this](https://www.youtube.com/watch?v=nTTZZyD4xlE). In future this will be referred to as side events. You could decrease the volume of the drums to have the snares drop automatically for example.
+
+This is something that you can't do in DAWs.
+
+```{code-block} js
+---
+force: true
+---
+
+
+
+async play() {
+ var self = this;
+ var cellx = window.cellx.cellx;
+
+ await Tone.start();
+ Tone.Transport.start();
+ Tone.Transport.bpm.value = this.state.tempo;
+ Tone.Transport.swing.value = 0;
+
+ var transition = function () {
+ }
+
+ var always = function () {
+ }
+
+
+ var render_loop = function () {
+ }
+
+ var animation = function () {
+ render_loop();
+ window.requestAnimationFrame(animation)
+ }
+
+ Tone.Master.mute = false;
+ document.getElementById('tempo-value').disabled = true;
+ document.getElementById('tick-value').disabled = true;
+
+ var mem = self.state.mem;
+ var handlers = {};
+ var count = -1;
+
+ var text = editor.getValue();
+ editor.on("change", function () {
+ text = editor.getValue();
+ });
+
+ var patterns = [ cellx("0000") ]; // need this for first eval
+
+ var bars = 0;
+ var tick = 0;
+
+ loop = new Tone.ToneEvent((time, chord) => {
+ count = count + 1;
+ tick = (count % this.state.ticks);
+ if (tick === 0) ++bars;
+
+ $("#duration").html("" + bars + "." + tick + " / " + count + " / " + window.roundTo(Tone.Transport.seconds, 2));
+
+ for (var i = 0; i < patterns.length; i++) {
+ var samples = this.state.samples;
+ var dials = self.state.dials;
+ var numbers = self.state.numbers;
+
+ if (document.getElementById('edit-mode').checked) {
+ p = oldPatterns[i];
+ } else {
+ var p = patterns[i];
+ oldPatterns[i] = p;
+ }
+ if (p && p.length !== 0) {
+ var track_no = i + 1;
+ var pattern = pattern_clean(p());
+ var isHit = (pattern.split('')[tick] == "1") ? true : false;
+
+ try {
+ if (document.getElementById('edit-mode').checked) {
+ eval(oldCode);
+ } else {
+ eval(text);
+ if (document.getElementById('load-mode').checked) {
+ document.getElementById('load-mode').checked = false;
+ transition();
+ }
+ oldCode = text;
+ }
+ $("#error").html("");
+ } catch (ex) {
+ $("#error").html(ex);
+ eval(oldCode);
+ }
+ }
+ }
+ }, []);
+ loop.loop = true;
+ loop.loopEnd = this.state.ticks + "n";
+ loop.start();
+
+ window.requestAnimationFrame(animation)
+
+}
+```
+
+## Dials
+
+Bitrhythm provides custom dials. These dials can be mapped to any aspects of Tone.js. All dials are available as an array dials in the live code editor.
+
+```{code-block} html
+---
+force: true
+---
+
+
+
+
+
+
+
+
+
+
+```
+
+## Numbers
+
+These numbers can be mapped to any aspect of Tone.js. All number boxes are available as an array numbers in the live code editor. Useful for debugging purposes.
+
+```{code-block} html
+---
+force: true
+---
+
+
+
+
+
+
+
+
+
+
+```
+
+## AutoKnob
+
+AutoKnob enables programmatic automation in Bitrhythm
+
+`x -> [1, 2.5, 4, 3.2] | by 0.3`
+
+x will go from 1 to 2.5 to 4 to 3.2 in increments of 0.3 for every tick. While x will increase till 4 ... it will decrease once it reaches 4 and drop down to 3.2. After reaching 3.2 you can stay there or reverse back. At any point during live editing, you can add an extra element to the array. If you add 5 for example, the loop will continue from 3.2 to 5.
+
+You can think of each element in the array as the "final knob position" and in each cycle we are moving to the next knob position in increments of 0.3
+
+An alternate to AutoKnob is to use TimedKnob. In the endless acid banger project, the basic code was using a simple timer to randomly move the knob position along with note collections and weighted random choice on note collections for generating rhythms.
+
+TimedKnobs can be used to add small variations in volume to make the drums sounds more natural.
+
+```{code-block} js
+---
+force: true
+---
+
+
+function knob(options) {
+ options = options || {};
+ var context = {};
+ context.ramp = options.ramp || [0 , 1];
+ context.count_skip = options.speed || 4;
+ context.step = options.step || 0.01;
+ context.reverse = options.reverse || true;
+ context.number = options.number || null;
+
+ context.current_count = 0;
+ context.index = 0;
+ context.val = window.cellx.cellx(options.initial || 0.5)
+
+ function changeContext() {
+ context.next_val = context.ramp[context.index + 1];
+
+ context.val(context.ramp[context.index]);
+ if (context.val() > context.next_val) {
+ context.direction = -1;
+ } else {
+ context.direction = 1;
+ }
+ }
+
+ changeContext();
+
+ return {
+ "cell": context.val,
+ "push": function (val) {
+ context.ramp.push(val);
+ },
+ "replace": function (val) {
+ context.ramp = val;
+ },
+ "speed": function (val) {
+ context.count_skip = val;
+ },
+ "step": function (val) {
+ context.step = val;
+ },
+ "up": function (val) {
+ val = val || 0.1;
+ context.ramp.push(context.ramp[context.ramp.length - 1] + val);
+ },
+ "down": function (val) {
+ val = val || -0.1;
+ context.ramp.push(context.ramp[context.ramp.length - 1] + val);
+ },
+ "move": function () {
+ if (context.current_count >= context.count_skip) {
+ context.current_count = 1;
+
+ if (context.direction == 1) {
+ var cmp = function () {
+ return (context.val() >= context.next_val);
+ };
+ } else {
+ var cmp = function () {
+ return (context.next_val >= context.val());
+ };
+ }
+
+ if (cmp()) {
+ context.val(context.next_val);
+ context.index = context.index + 1;
+ if (context.index === context.ramp.length -1) {
+ if (context.reverse) {
+ context.index = 0;
+ context.ramp = context.ramp.reverse();
+ } else {
+ context.index = context.index - 1;
+ }
+ }
+ changeContext();
+ context.val(context.val() + context.step * context.direction);
+ if (context.number) context.number(context.val());
+ } else {
+ context.val(context.val() + context.step * context.direction);
+ if (context.number) context.number(context.val());
+ }
+ } else {
+ context.current_count += 1;
+ }
+
+ return context.val();
+ }
+ }
+}
+
+function timedKnob(options) {
+ options = options || {};
+ var context = {};
+ context.interval = options.interval || 100;
+ context.knob = knob(options);
+
+ context.timer = setInterval(function () {
+ context.knob.move();
+ }, context.interval);
+
+ context.knob["clear"] = function () {
+ clearInterval(context.timer);
+ }
+
+ return context.knob;
+}
+```
+
+## Main UI
+
+
+```
+// Not working
+
+.CodeMirror-selected,
+.CodeMirror-focused,
+.CodeMirror-activeline,
+.CodeMirror-activeline-background {
+ background: transparent;
+ color: #882d2d;
+ z-index: 5 !important;
+}
+```
+
+
+```{code-block} html
+---
+force: true
+---
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Sample
+
+You can add samples using the file upload. All samples are available as an array – samples. Initialise samples, global variables and synthesisers using the transition function and change the sample parameters using the same during live coding.
+
+```{code-block} html
+---
+force: true
+---
+
+
+
+
+
+
+
+ { getLast(this.props.ti -1)}
+ (x)
+
+
+
+
+
+
+```
+
+
+## App
+
+```{code-block} hylang
+---
+force: true
+---
+
+(import [sanic [Sanic response]])
+(import [sanic.response [json text]])
+(import [sanic.exceptions [NotFound abort]])
+(import [jinja2 [Environment FileSystemLoader]])
+(import re)
+(import ipdb)
+(import sys)
+(import traceback)
+(import json)
+(import datetime)
+(import [email.utils [format_datetime]])
+(import [urllib.parse [urlparse]])
+(import base64)
+
+(setv file-loader (FileSystemLoader "templates"))
+(setv env (Environment :loader file-loader))
+
+(setv app (Sanic "Bitrhythm"))
+
+(with-decorator
+ (app.exception NotFound)
+ (defn/a ignore_404s [request exception]
+ (return (text (+ "Yep, I totally found the page " request.url)))
+ )
+)
+
+(with-decorator
+ (app.route "/song/")
+ (defn/a get-index [request name]
+ (setv template (env.get_template "index.html"))
+ (return (response.html (template.render {"data" name})))
+ )
+)
+
+(with-decorator
+ (app.route "/")
+ (defn/a get-index [request]
+ (setv template (env.get_template "index.html"))
+ (return (response.html (template.render {"data" ""})))
+ )
+)
+
+(with-decorator
+ (app.route "/issue")
+ (defn/a get-index [request]
+ (setv template (env.get_template "page.html"))
+ (return (response.html (template.render)))
+ )
+)
+
+(app.static "/" "./public")
+
+(defmain [&rest args]
+ (app.run :host "0.0.0.0" :port 8015)
+)
+
+```
+
+
+## Index
+
+```{code-block} html
+---
+force: true
+---
+
+
+
+
+
+
+
+ Bitrhythm
+
+
+
+
+
+
+
+
+ ${common_scripts}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+
+## Page
+
+```{code-block} html
+---
+force: true
+---
+
+
+
+
+
+
+
+
+
+ About birthythm
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+## Javascript
+
+```{code-block} js
+---
+force: true
+---
+
+
+function initWinamp(preset) {
+ var can = document.getElementById("visual");
+ can.height = window.innerHeight - document.getElementById("header-playback").clientHeight - 75;
+ can.width = window.innerWidth;
+ var can_container = document.getElementById("canvas-container");
+ can_container.width = window.innerWidth;
+ var visualizer = window.butterchurn.default.createVisualizer(Tone.getContext().rawContext, can, {
+ height: window.innerHeight - document.getElementById("header-playback").clientHeight - 75,
+ width: window.innerWidth,
+ meshWidth: 24,
+ meshHeight: 18,
+ });
+ visualizer.connectAudio(Tone.getContext().destination);
+ const presets = window.butterchurnPresets.getPresets();
+ const presetParam = presets[preset];
+ visualizer.loadPreset(presetParam, 0.0); // 2nd argument is the number of seconds to blend presets
+ return visualizer;
+}
+
+function guard(range) {
+ var state = null;
+ return function (val) {
+ if ((val >= range[0]) && (val <= range[1])) {
+ state = val;
+ return val;
+ } else {
+ return state;
+ }
+ }
+}
+
+function hex2bin(hex) {
+ var letters = hex.replace('`1','a');
+ letters = hex.replace('`2','b');
+ letters = hex.replace('`3','c');
+ letters = hex.replace('`4','d');
+ letters = hex.replace('`5','e');
+ letters = hex.replace('`6','f');
+ letters = letters.split('');
+ var bin = "";
+ letters.map(function(letter) {
+ if (letter == "0") {
+ bin += "0000";
+ }
+ if (letter == "1") {
+ bin += "0001";
+ }
+ else if (letter == "2") {
+ bin += "0010";
+ }
+ else if (letter == "3") {
+ bin += "0011";
+ }
+ else if (letter == "4") {
+ bin += "0100";
+ }
+ else if (letter == "5") {
+ bin += "0101";
+ }
+ else if (letter == "6") {
+ bin += "0110";
+ }
+ else if (letter == "7") {
+ bin += "0111";
+ }
+ else if (letter == "8") {
+ bin += "1000";
+ }
+ else if (letter == "9") {
+ bin += "1001";
+ }
+ else if (letter == "a") {
+ bin += "1010";
+ }
+ else if (letter == "b") {
+ bin += "1011";
+ }
+ else if (letter == "c") {
+ bin += "1100";
+ }
+ else if (letter == "d") {
+ bin += "1101";
+ }
+ else if (letter == "e") {
+ bin += "1110";
+ }
+ else if (letter == "f") {
+ bin += "1111";
+ }
+
+ })
+ return bin;
+}
+
+
+function pattern_clean(p) {
+ if (!p) {
+ return "";
+ }
+ p = p.replace(/ /g, "");
+ var fc = p.split('')[0];
+ if (fc== "p") {
+ var ptype = "xo";
+ var l = (p.length - 1);
+ }
+ else if (fc== "b") {
+ var l = (p.length - 1);
+ var ptype = "bin";
+ }
+ else {
+ var ptype = "hex";
+ var l = (p.length) * 4;
+ }
+
+ if (ptype == "bin") {
+ var fp = p.substring(1);
+ } else if (ptype == "xo") {
+ var fp = p.substr(1);
+ fp = fp.replace(/x/g, "1");
+ } else {
+ var fp = hex2bin(p);
+ }
+
+ return fp;
+}
+
+function download(data, filename, type) {
+ var file = new Blob([data], { type: type });
+ if (window.navigator.msSaveOrOpenBlob) // IE10+
+ window.navigator.msSaveOrOpenBlob(file, filename);
+ else { // Others
+ var a = document.createElement("a"),
+ url = URL.createObjectURL(file);
+ a.href = url;
+ a.download = filename;
+ document.body.appendChild(a);
+ a.click();
+ setTimeout(function () {
+ document.body.removeChild(a);
+ window.URL.revokeObjectURL(url);
+ }, 0);
+ }
+}
+
+// https://stackoverflow.com/questions/15762768/javascript-math-round-to-two-decimal-places
+function roundTo(n, digits) {
+ var negative = false;
+ if (digits === undefined) {
+ digits = 0;
+ }
+ if (n < 0) {
+ negative = true;
+ n = n * -1;
+ }
+ var multiplicator = Math.pow(10, digits);
+ n = parseFloat((n * multiplicator).toFixed(11));
+ n = (Math.round(n) / multiplicator).toFixed(digits);
+ if (negative) {
+ n = (n * -1).toFixed(digits);
+ }
+ return n;
+}
+
+${knob_code}
+```
+
ADDED source/midi.md
Index: source/midi.md
==================================================================
--- source/midi.md
+++ source/midi.md
@@ -0,0 +1,147 @@
+# MIDI and DAW Terms
+
+## MIDI
+
+MIDI changed music.
+
+Consider what a classically trained Musician needs to do, to make his
+music heard.
+
+He needs to,
+
+1. Write it
+2. Get a bunch of musicians
+3. Practice
+4. Play
+5. goto 1
+6. Record the Final Cut in a Studio
+
+With MIDI,
+
+1. Write it. Save it, with a Software
+2. Get the electronic instruments to play it for him
+ -or-
+ Get a computer Software to play it for him
+3. Tweak
+4. Record the Final Cut in Software or a Studio
+
+MIDI is - Musical Instrument Digital Interface.
+
+It abstracts Music Notation into bytes.
+Since its bytes, you can replay it with software.
+
+MIDI made production,
+
+* Dirt Cheap
+* Made Producers more efficient
+
+How about Audio Quality ?
+
+* A Classical Ensemble still has its place. Panning is unique there.
+* But MIDI + Electronic Instrument increased the Range. It helped form whole new genres in Music like EDM, IDM, SynthPunk ...
+
+Limitations ?
+
+* MIDI is limited to 16 instruments.
+* Integers.
+* 16 is a big number.
+* OSC overcomes these.
+
+Usage
+=====
+
+To use MIDI you need,
+
+1. A Sequencer
+
+ This generates MIDI messages like,
+
+ ```
+ Command param1 param2 param3
+ Command param1 param2
+ Command param1 param2 param3
+ Command param1 param2
+ ...
+ ```
+
+ Important messages are,
+
+ 1. Note On => Channel [1-16]**, Pitch[0-127], Volume[0-127]
+ 2. Note Off "
+ 3. Controller => No [0-127], Value[0-127]
+
+ ** Channel 10 is reserved for Drums.
+
+ Note On and Note Off are typically sent by keys, buttons.
+ Controller Messages are typically sent by knobs and pedals.
+
+2. A Synthesizer
+
+ This accepts MIDI data.
+ It Maps channels to DSP.
+ It Maps knobs** to effects like distortion and reverb.
+
+ ** The positions of the knob are called patches.
+
+3. A Sampler.
+
+ This accepts MIDI data.
+ It Maps channels to sound recordings.
+ It Maps knobs to effects like distortion and reverb.
+
+If you buy Music Hardware you have both 1 and 2. The sequencing capabilities are limited in a synth, so you can't Edit MIDI data in the instrument.
+
+Some notes,
+
+0. You can buy hardware and get a Synthesizer + a limited Sequencer.
+
+1. You can buy A Sequencer and use a Computer as a Synthesizer
+
+or
+
+2. Use a Software Sequencer to Program your Synthesizer
+
+or
+
+3. Forget Hardware and go complete nuts with Software
+
+
+
+Also, Robots love it.
+
+## Terms
+
+Instruments used in Electronic Music
+
+- Tr 909
+- Tr 303
+- Tr 808
+
+Instruments commonly used in Disco, Synth Wave and Funk
+
+- Samplers
+- FM Synthesiser
+
+Rock Pedals used in
+
+- Reverb (sludge)
+- Delay (slow rock)
+- Distortion (metal)
+
+DAW Terms
+
+- track
+- sequencer
+- piano roll
+- OSC
+- sampler
+- soundfont
+- synthesizer (DSP, FM, AM, AS, SS, PM)
+- effect
+- mixer, LFO
+- automation, equalizer
+- filter
+- arpegiator
+
+
+
ADDED source/samples.md
Index: source/samples.md
==================================================================
--- source/samples.md
+++ source/samples.md
@@ -0,0 +1,13 @@
+# Samples
+
+You can use Sample URL to add samples from anywhere. Currently there are three samples available from the main site for use in the demos.
+
+ - /Kick01.wav
+ - /Snare19.wav
+ - /Closedhat01.wav
+- /MiscSynthStab04.wav
+- /Perc11.wav
+- /PianoStab03.wav
+- /Snare06.wav
+- /Snare12.wav
+- /Snare09.wav
ADDED source/saving.md
Index: source/saving.md
==================================================================
--- source/saving.md
+++ source/saving.md
@@ -0,0 +1,7 @@
+# Saving and Sharing
+
+There is a save button that converts the state of bitrhythm into a url paramter. This means there is an upper limit on how much code you can write, making it useful for demoscene like situations. You can share the url with tinyurl.
+
+To make a complete song use `bars` to trigger multiple transitions.
+
+
ADDED source/source-code.md
Index: source/source-code.md
==================================================================
--- source/source-code.md
+++ source/source-code.md
@@ -0,0 +1,15 @@
+# Source Code and License
+
+The literate code framework wheel uses extensively in this project is available [here](https://xyzzyapps.link/wheel).
+
+The documentation, demo code and the source code in the following webpages is distributed under [creative commons, attribution-noncommercial-sharealike license](https://creativecommons.org/licenses/by-nc-sa/4.0/). The source code is self-hosted using fossil [here](https://fossil.xyzzyapps.link/bitrhythm/timeline). You can report issues, browse and download the source code from fossil. An Iframe is provided below. You can also get the latest source code [here](https://fossil.xyzzyapps.link/bitrhythm/download).
+
+
+
+Coding conventions
+
+1. 4 Spaces
+2. EcmaScript5
+
+Written using the Vim editor.
+
ADDED source/tweaking.md
Index: source/tweaking.md
==================================================================
--- source/tweaking.md
+++ source/tweaking.md
@@ -0,0 +1,121 @@
+# What exactly is Tweaking ?
+
+For that you need some context.
+
+Historically speaking, music was an accompaniment for theatrical plays. A typical song has a Theme and develops Exposition / Conflict / Resolution through time. Its not a coincidence that the sonata and the dramatic structure share the same form. In a typical play you have scenes, transitions, sets in the background and characters in the foreground. While the play progresses and characters clash you get music alongside each of these elements. Every song has a title that guides the listener to what the song is about. Typical Scenes include intro, bridge, buildup, section, breakdown and climax. What is the intro about ? What is the bridge about ? What is the verse about ? What is the chorus about ? What are the sections about ? The essential question driving any story is how to be ? In a drama this question is answered with doubts, emotion, circularity, warnings from the chorus, exposition, rises, falls, reprisals, conclusions, contradictions, denouncements, confrontations, mistakes, reversals, recognition, revelation resolution, statements and breakdowns.
+
+Modern music has evolved to be standalone but it retains elements of theatrical story telling. You can divide a song into verses and sections. In the documentation, we will use the terms from plays. Within each musical scene you typically have,
+
+- Background: You use layers of rhythm and groove to set the mood.
+- Foreground: Lead (Soliloquy), Counterpoint (Character Conflict), Call Response(Dialogues), Motif, Ornaments (Exposition)
+
+In Electronic Music, the foreground is mainly developed using Layers(polyrhythms) and Tweaking. Tweaking and Layers effectively function for the purposes of Exposition and Conflict. Chorus, Fade, Drop are used for transitions typically in electronic music. Tweaking is also called knob-twisting by hardware users. To compare the foreground in others,
+
+Classical Music: Counterpoint and Harmony
+Rock: Call Response
+
+Now EDM - Dub, Techno and Jungle - are distinct musical genres. What could they possibly share ?
+
+1. All three uphold the primacy of Rhythm for start.
+2. All three are instrumental.
+3. Voices are used sparingly as samples.
+4. All three have a tradition of live acts.
+5. Dub Plates are common for these genres.
+6. Melody is nothing but a repeating stab (motif)
+
+If you want to add a solo to EDM, you typically add a live instrument.
+
+Dub Music is made by messing with the Recording Engineer's Mixing desk. A prime example of tweaking.
+
+VIDEO
+
+Techno is made by messing with Hardware or Hardware + Software. Tweaking + Layering.
+
+VIDEO
+
+Jungle is used as a catch all for Breakbeat, Hardcore as well. Jungle Music is made by messing with Hardware + Software combo. Complex Layering is used here.
+
+VIDEO
+
+Bitrhythm only supports rhythmic notations and is geared towards electronic music. My current goal is to produce an IDM track and Chiptune with it with complex rhythmic patterns. As for techno the current setup seems alright. Support will be added in the future for pitch manipulation in a different module or a different project. There is a piano module in the code with midi support but its currently deactivated.
+
+You can treat a musical scale as no different than a drumset. The pentatanic scale approximately maps to a drumset and you can use techniques from [melodic drumming](https://www.youtube.com/watch?v=1qdyxML4tF0) with multiple samples. This is typically how drummers approach guitars.
+
+## Is Bitrhythm Maths heavy ?
+
+You will need some basic maths operations to understand and use bitrhythm effectively. These operations are about working with lists of numbers and lists of strings.
+
+Some common operations on lists include
+
+- Rotate
+- Invert
+- Slicing
+- Choose
+- Reverse
+- Shuffle
+- Filter
+- Map
+
+Other math operations are useful for working with envelope ranges,
+
+- Clamp
+- Ramp
+- Waves
+
+Programming is not that difficult. Programming is about organising reactions to events. A typical event comes from
+
+1. The timer
+2. User interface elements like sliders and dials
+3. Data change
+
+A typical reaction to an event (often called a handler) either changes data or displays data. A data change triggers more events. Other programming concepts needed for birthythm
+
+As Tone.js provides the bulk of sound generation in Bitrhythm you should make youself comfortable with its APIs.
+
+You will also need to understand
+
+- Conditions
+- Loops
+- Functions
+- Javascript Math.random
+- Modulo Arithmetic
+- SetInterval Timers
+
+What comes next - This is the pertinent question when composing music. If you are feeling adventurous you can try to use,
+
+1. Mathematical patterns like fibonacci series, geometric series,
+pi, fractals ...
+2. Neo-Riemannian_theory, Combinatory theory
+3. Sacred Geometry
+4. Circle of fifths, chord progressions
+5. Set transformations like inversion on Scales
+6. Licks from books
+7. Randomness, I Ching, Playing Cards
+
+## DJ Controls
+
+```{todo}
+Expand
+```
+
+Essentially a DJ is like a conductor. Loops are like individual instrumentalists.
+
+1. Cueing -> Mute / Solo
+2. Dynamics -> Volume
+3. Ornamentation -> Cutoff
+5. Tempo, Beat Counting -> Beat Matching
+
+## Music is arrangement
+
+VIDEO
+
+## See Also
+
+In screenplays you also have a [setup / payoff](https://www.youtube.com/watch?v=cG5zKwmXLgo) dynamic.
+
+Setup
+- Obstacles
+- Challenges
+- Forces
+- Context
+
ADDED source/what.cog
Index: source/what.cog
==================================================================
--- source/what.cog
+++ source/what.cog
@@ -0,0 +1,77 @@
+# What is bitrhythm about ?
+
+Currently this is how you can use code with music,
+
+1. VST plugins
+2. DAW scripting
+3. Embedded code to interact with electronics
+4. ML approaches
+5. Live coding
+6. Use some weird algorithms / number sequences / hardcoded chords / random numbers
+7. Web Audio / Audio Programming languages
+
+Bitrhythm offers 4-7. In Bitrhythm you get a coding playground to explore and learn webaudio and webgl apis for the purposes of
+
+1. live visual programming
+2. live audio programming
+3. music sequencing
+4. interactive music
+5. audio visualisation
+6. music learning
+
+Apart from the core html apis, you can also use the following libraries
+
+1. Tuna
+2. Timbral
+3. Tone.js
+4. Underscore / Rambda
+5. cellx
+6. Magenta.js (Machine Learning)
+7. Three.js / P5.js / D3 / Paper.js
+8. Winamp Visualisations (butterchurn)
+
+```{code-block} html
+---
+force: true
+---
+@<
+import common
+cog.out(common.external_libraries)
+@>
+@@
+```
+
+## About Author
+
+I make [web apps](https://xyzzyapps.link) apps for a living. I started messing with algorithmic composition using pure data around 2011. Although nothing much came out of it musically, it transformed the way I view programming. The unix pipes concept is identical to the data flow model in pure data; albeit with more connections.
+
+For my music I moved onto Electribe, Renoise, Reaper and live looping with my guitar. I started exploring sonic pi in 2019 but TBH none of the live coding tools were doing what I wanted - basically tweaking /knob-twisting in techno so I started my work on this project. [This library](https://www.youtube.com/watch?v=adz1Gv5Lm34) in sonic pi comes close to what I am trying todo with this project. In bitrhythm time is divided into ticks like renoise tracker.
+
+After my initial work on the project in 2019, the project got sidetracked. After seeing [endless acid banger](https://www.vitling.xyz/toys/acid-banger/) I had an epiphany to work on this project again. I was able to compose an endless and interactive dubtechno demo track. In March 2021, I also picked up on literate programming so this document will have both code and documentation intertwined.
+
+The documentation for the literate programming project - wheel is [available here](https://xyzzyapps.link/wheel/).
+
+## Support this project
+
+My goal is to make bitrhythm easy for both professionals and music learners. Support my work to get regular updates. Supporters will also get free accounts when I add pro features! Also get notified on latest developments and tutorials in the web audio world.
+
+
Support Bitrhythm
+
+You can find me on [theaudioprogrammer](https://theaudioprogrammer.com/) and algorave discord as @xyzzy.
+
+## Future Features
+
+- Saving Songs
+- More Synths with Presets
+- Visualisation Presets
+- Autocomplete Rhythms and Chords
+- Automation Recording
+- Version Control
+- Record
+- Midi Support
+- More tutorials and examples on Web APIs
+- Synthesizer
+- Collaboration
+- Adding DJ Transition / Hints support
+
+
ADDED source/what.md
Index: source/what.md
==================================================================
--- source/what.md
+++ source/what.md
@@ -0,0 +1,97 @@
+# What is bitrhythm about ?
+
+Currently this is how you can use code with music,
+
+1. VST plugins
+2. DAW scripting
+3. Embedded code to interact with electronics
+4. ML approaches
+5. Live coding
+6. Use some weird algorithms / number sequences / hardcoded chords / random numbers
+7. Web Audio / Audio Programming languages
+
+Bitrhythm offers 4-7. In Bitrhythm you get a coding playground to explore and learn webaudio and webgl apis for the purposes of
+
+1. live visual programming
+2. live audio programming
+3. music sequencing
+4. interactive music
+5. audio visualisation
+6. music learning
+
+Apart from the core html apis, you can also use the following libraries
+
+1. Tuna
+2. Timbral
+3. Tone.js
+4. Underscore / Rambda
+5. cellx
+6. Magenta.js (Machine Learning)
+7. Three.js / P5.js / D3 / Paper.js
+8. Winamp Visualisations (butterchurn)
+
+```{code-block} html
+---
+force: true
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## About Author
+
+I make [web apps](https://xyzzyapps.link) apps for a living. I started messing with algorithmic composition using pure data around 2011. Although nothing much came out of it musically, it transformed the way I view programming. The unix pipes concept is identical to the data flow model in pure data; albeit with more connections.
+
+For my music I moved onto Electribe, Renoise, Reaper and live looping with my guitar. I started exploring sonic pi in 2019 but TBH none of the live coding tools were doing what I wanted - basically tweaking /knob-twisting in techno so I started my work on this project. [This library](https://www.youtube.com/watch?v=adz1Gv5Lm34) in sonic pi comes close to what I am trying todo with this project. In bitrhythm time is divided into ticks like renoise tracker.
+
+After my initial work on the project in 2019, the project got sidetracked. After seeing [endless acid banger](https://www.vitling.xyz/toys/acid-banger/) I had an epiphany to work on this project again. I was able to compose an endless and interactive dubtechno demo track. In March 2021, I also picked up on literate programming so this document will have both code and documentation intertwined.
+
+The documentation for the literate programming project - wheel is [available here](https://xyzzyapps.link/wheel/).
+
+## Support this project
+
+My goal is to make bitrhythm easy for both professionals and music learners. Support my work to get regular updates. Supporters will also get free accounts when I add pro features! Also get notified on latest developments and tutorials in the web audio world.
+
+ Support Bitrhythm
+
+You can find me on [theaudioprogrammer](https://theaudioprogrammer.com/) and algorave discord as @xyzzy.
+
+## Future Features
+
+- Saving Songs
+- More Synths with Presets
+- Visualisation Presets
+- Autocomplete Rhythms and Chords
+- Automation Recording
+- Version Control
+- Record
+- Midi Support
+- More tutorials and examples on Web APIs
+- Synthesizer
+- Collaboration
+- Adding DJ Transition / Hints support
+
+
ADDED templates/index.html
Index: templates/index.html
==================================================================
--- templates/index.html
+++ templates/index.html
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+ Bitrhythm
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ADDED templates/page.html
Index: templates/page.html
==================================================================
--- templates/page.html
+++ templates/page.html
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+ About birthythm
+
+
+
+
+
+
+
+
+
+
ADDED wheel.sh
Index: wheel.sh
==================================================================
--- wheel.sh
+++ wheel.sh
@@ -0,0 +1,37 @@
+#!/usr/local/Cellar/fish/3.2.2_1/bin/fish
+
+set DEV "1"
+
+function build
+ kill -9 (cat ./pidfile)
+ rm source/main.md
+ rm source/what.md
+ ./env/bin/cog --markers="@< @> @@" -D DEV=$DEV -d source/main.cog > source/main.md
+ ./env/bin/cog --markers="@< @> @@" -D DEV=$DEV -d source/what.cog > source/what.md
+ hy bitrhythm.hy &
+ set PID %1
+ rm -rf build
+ make html
+ echo $PID > ./pidfile
+end
+
+function final
+ set DEV "0"
+ build
+end
+
+function pack
+ rm bitrhythm.tgz
+ gtar -czv --exclude "public/closed" --exclude "_sources" --exclude "searchindex.js" --exclude ".DS_Store" --exclude "*.inv" --exclude "__init__.py" --exclude "doctrees" --exclude "__pycache__" --exclude "*.tcl" --exclude "*.tgz" --exclude "draft" --exclude "*env*" --exclude "tags" --exclude "tags.*" --exclude "*.git" -f bitrhythm.tgz .
+end
+
+switch $argv[1]
+ case "--prod"
+ final
+ case "--dev"
+ build
+ case "--zip"
+ pack
+end
+
+