diff --git a/docs/funcs.md.template b/docs/funcs.md.template new file mode 100644 index 00000000..168c9be9 --- /dev/null +++ b/docs/funcs.md.template @@ -0,0 +1,264 @@ +{{! This is a mustache template for the Hydra function documentation. + The funcs.md file is generated by `scripts/docs.js` using this template. +}} +# Functions + +- [Categories of functions](#categories) +- [Complete contents of functions](#contents) + +## Categories + +- [Audio](#audio) +- [Color](#color) +- [Geometry](#geometry) +- [Global variables](#global-variables) +- [Modulators](#modulators) +- [Operators](#operators) +- [Sources](#sources) +- [Parameter sequences](#parameter-sequences) + +## Contents + +- [Audio](#audio) + - [hide](#hide) + - [setBins](#setbins) + - [setCutoff](#setcutoff) + - [setScale](#setScale) + - [setSmooth](#setSmooth) + - [show](#show) +- [Color](#color) +{{ category-toc-color }} +- [Geometry](#geometry) +{{ category-toc-geometry }} +- [Global variables](#global-variables) + - [mouse](#mouse) + - [time](#time) +- [Modulators](#modulators) +{{ category-toc-modulators }} +- [Operators](#operators) +{{ category-toc-operators }} +- [Sources](#sources) +{{ category-toc-sources }} + - [out](#out) + - [render](#render) +- [Parameter sequences](#parameter-sequences) + - [Lists as parameter sequences](#lists-as-parameter-sequences) + - [Functions on parameter sequences](#functions-on-parameter-sequences) + - [fast](#fast) + +--- + +## Audio + +Functions for manipulating audio signals. + +- [hide](#hide) +- [setBins](#setbins) +- [setCutoff](#setcutoff) +- [setScale](#setScale) +- [setSmooth](#setSmooth) +- [show](#show) + +### hide + +`.hide()` + +### setBins + +`.setBins( bins )` + +* `bins` :: integer (default `x`) + +### setCutoff + +`.setCutoff( frequency )` + +* `frequency` :: float (default `x`) + +### setScale + +`.setScale( amount )` + +* `amount` :: float (default `x`) + +### setSmooth + +`.setSmooth( amount )` + +* `amount` :: float (default `x`) + +### show + +`.show()` + +--- + +## Color + +Functions for manipulating color. + +{{ category-toc-color }} + +{{& functions-color }} + +--- + +## Geometry + +Functions for manipulating geometry. + +{{ category-toc-geometry }} + +{{& functions-geometry }} + +--- + +## Global variables + +Useful variables that are defined globally, and can be used within functions as a parameter. + +- [mouse](#mouse) +- [time](#time) + +### mouse + +`mouse` + +* `.x` :: x position of mouse +* `.y` :: y position of mouse + +#### Example + +Control the oscillator frequency with the mouse position: + +```javascript +osc(() => mouse.x).out(o0) +``` + +### time + +`time` + +* `time` :: the current time + +#### Example + +Control the oscillator using a sine wave based on the current time: + +```javascript +osc( ({time}) => Math.sin(time) ).out(o0) +``` + +--- + +## Modulators + +Functions for describing modulations of sources. + +{{ category-toc-modulators }} + +{{& functions-modulators }} + +## Operators + +Functions for performing operations on sources. + +{{ category-toc-operators }} + +{{& functions-operators }} + +--- + +## Sources + +Sources are elementary generators that output different types of visual content. + +{{ category-toc-sources }} + - [out](#out) + - [render](#render) + +{{& functions-sources }} + +### out + +`.out( buffer )` + +* `buffer` + * `osc`: `o0`, `o1`, `o2`, `o3` + * `src`: `s0`, `s1`, `s2`, `s3` + +#### Example + +```javascript +// output four oscillators to different buffers +// and then modulate them together +osc( [1,10,50,100,250,500].fast(2) ).kaleid(20).out(o0) // frequency +osc( ({time}) => Math.sin(time/10) * 100 ).kaleid(19).out(o1) // frequency 2 +osc( 10, [-10,-1,-0.1,0,0.1,1,10], 0 ).kaleid(21).out(o2) // sync +osc(10,0.1, ({time}) => Math.sin(time/10) * 1 ) // offset + .modulate(o1,0.05) + .modulate(o2,0.05) + .modulate(o3,0.05) + .kaleid(20) + .add(noise(3,10)) + .out(o3) +render(o3) +``` + +### render + +`render( buffer )` + +* `buffer`: buffer (default `o0`) + +#### Example + +```javascript +osc( [1,10,50,100,250,500].fast(2) ).out(o0) // frequency +osc( ({time}) => Math.sin(time/10) * 100 ).out(o1) // frequency 2 +osc( 10, [-10,-1,-0.1,0,0.1,1,10], 0 ).out(o2) // sync +osc(10,0.1, ({time}) => Math.sin(time/10) * 100 ).out(o3) // offset + +render(o0) // change to o1, o2, or o3 +``` + +```javascript +// see all four buffers at once +osc( [1,10,50,100,250,500].fast(2) ).out(o0) // frequency +osc( ({time}) => Math.sin(time/10) * 100 ).out(o1) // frequency 2 +osc( 10, [-10,-1,-0.1,0,0.1,1,10], 0 ).out(o2) // sync +osc(10,0.1, ({time}) => Math.sin(time/10) * 100 ).out(o3) // offset +render() +``` + +--- + +## Parameter sequences + +- [Lists as parameter sequences](#lists-as-parameter-sequences) +- [Functions on parameter sequences](#functions-on-parameter-sequences) + - [fast](#fast) + +### Lists as parameter sequences + +``` +osc( + [80, 100, 200, 50], 1 ) +) +.out(o0) +``` + +### Functions on parameter sequences + +#### fast + +`fast ( amount) ` + +* `amount` :: float (default `x`) + +``` +osc( + [80, 100, 200, 50].fast(0.2), 1 ) +) +.out(o0) +``` diff --git a/scripts/docs.js b/scripts/docs.js index 913f957c..1b2c350e 100755 --- a/scripts/docs.js +++ b/scripts/docs.js @@ -1,61 +1,138 @@ -#! /usr/bin/env node +#!/usr/bin/node +// Script to build documentation from source. const fs = require('fs') -const glslfuncs = require('hydra-synth/src/composable-glsl-functions.js') +const glslTransforms = require('hydra-synth/src/composable-glsl-functions.js') +const Mustache = require('mustache') -const [,, outPath] = process.argv +const masterTemplateFile = 'docs/funcs.md.template'; -const categories = {} -const argtypes = {} - -Object.keys(glslfuncs).forEach((k) => { - const f = glslfuncs[k] - const categoryName = f.type.charAt(0).toUpperCase() + f.type.slice(1) - categories[categoryName] = categories[categoryName] || [] - categories[categoryName].push(k) - const inputs = f.inputs || [] - inputs.forEach((i) => { - argtypes[i.type] = i.type - }) -}) - -const createlist = (items) => items.map((i) => `* ${i}`).join('\n') - -const createFuncDoc = (fname) => { - const title = `#### ${fname}` - const argnames = (glslfuncs[fname].inputs || []).map(i => `${i.name} :: ${i.type}`) - - let args - if (argnames.length > 1) { - args = `#### Args\n${createlist(argnames)}` - } else { - args = 'No Args' - } +const functionTemplate = ` +### {{ name }} + +\`{{ prototype }}\` + +{{# inputs }}* \`{{ name }}\` :: {{& type }}{{# default }} (default \`{{ default }}\`){{/ default }} +{{/inputs}} +{{# description }} + +{{& description }} +{{/ description }} +{{# example }} + +#### Example + +\`\`\`javascript +{{& example }} +\`\`\`{{/ example }}` - return `${title}\n\n${args}\n` +var categories = { + 'color': [], + 'geometry': [], + 'operators': [], + 'modulators': [], + 'sources': [], } -const createCategory = (cname) => `### ${cname} +const categoryMap = { + 'color': 'color', + 'combine': 'operators', + 'combineCoord': 'modulators', + 'coord': 'geometry', + 'src': 'sources', +} -${categories[cname].map(createFuncDoc).join('\n')} -` +function stripInitialWhitespace(paragraph) { + if (paragraph === undefined) + return undefined; -const output = `# Functions + if (paragraph[0] == '\n') + paragraph = paragraph.slice(1) -## Argument Types + let firstCharacterIndex = paragraph.match(/[^ \n]/).index; + let lines = []; + for (line of paragraph.split('\n')) { + lines.push(line.slice(firstCharacterIndex)); + } + return lines.join('\n'); +} -${createlist(Object.keys(argtypes).sort())} +function renderFunctionPrototype(name, info) { + let parameters = info['inputs'].map((input) => input.name).join(', '); + if (info['type'] == 'src') + return `${name}( ${parameters} )`; + else + return `.${name}( ${parameters} )`; +} -## Functions +function processFunctionInputs(inputs) { + return inputs.map((entry) => { + if (entry['default'] != undefined) + entry['default'] = entry['default'].toString(); + return entry; + }); +} -${Object.keys(categories).sort().map(createCategory).join('\n')} +function renderFunction(name, info) { + let view = { + 'name': name, + 'description': stripInitialWhitespace(info['description']), + 'example': stripInitialWhitespace(info['example']), + 'prototype': renderFunctionPrototype(name, info), + 'inputs': processFunctionInputs(info['inputs']), + } -` + let markdown = Mustache.render(functionTemplate, view); + return markdown; +} -fs.writeFile(outPath, output, function (err) { - if (err) { - return console.log(err) +function renderFunctions(functions) { + return functions.sort().map((name) => renderFunction(name, glslTransforms[name])).join('\n'); +} + +function renderTableOfContents(functions) { + return functions.sort().map((name) => ` - [${name}](#${name.toLowerCase()})`).join('\n'); +} + +function main(outPath) { + for (name in glslTransforms) { + method = glslTransforms[name]; + if (name[0] == '_' || method.type == 'util') + console.log(`Ignoring private function ${name}`); + else if (! method['inputs']) + console.log(`Ignoring function with no inputs ${name}`) + else { + let cat = categoryMap[method['type']]; + + categories[cat].push(name); + } } - console.log('The file was saved!') -}) + fs.readFile(masterTemplateFile, 'utf-8', (err, masterTemplateData) => { + if (err) throw err; + + let view = {}; + + for (cat in categories) { + view[`category-toc-${cat}`] = renderTableOfContents(categories[cat]) + view[`functions-${cat}`] = renderFunctions(categories[cat]) + } + + let output = '\n' + + Mustache.render(masterTemplateData, view); + + fs.writeFile(outPath, output, (err) => { + if (err) throw err; + }); + }); +}; + +const [,, outPath] = process.argv + +if (! outPath) { + console.log(`Usage: ${process.argv[1]} docs/funcs.md`); + process.exit(1); +} + +main(outPath);