diff --git a/.prettierignore b/.prettierignore index d962cf6b..788d0e25 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,3 +6,4 @@ **/dist packages/mini/krill-parser.js packages/xen/tunejs.js +paper \ No newline at end of file diff --git a/package.json b/package.json index 5841995d..73c77740 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "lint": "eslint . --ext mjs,js --quiet", "codeformat": "prettier --write .", "format-check": "prettier --check .", - "check": "npm run format-check && npm run lint && npm run test" + "check": "npm run format-check && npm run lint && npm run test", + "iclc": "cd paper && pandoc --template=pandoc/iclc.html --citeproc --number-sections iclc2023.md -o iclc2023.html && pandoc --template=pandoc/iclc.latex --citeproc --number-sections iclc2023.md -o iclc2023.pdf" }, "workspaces": [ "packages/*" diff --git a/paper/Makefile b/paper/Makefile new file mode 100644 index 00000000..6b92561b --- /dev/null +++ b/paper/Makefile @@ -0,0 +1,16 @@ +all: iclc2023.pdf iclc2023.html + +clean: + rm iclc2023.pdf iclc2023.html + +iclc2023.html: iclc2023.md citations.json + pandoc --template=pandoc/iclc.html --citeproc --number-sections iclc2023.md -o iclc2023.html + +iclc2023.pdf: iclc2023.md citations.json pandoc/iclc.latex pandoc/iclc.sty + pandoc --template=pandoc/iclc.latex --citeproc --number-sections iclc2023.md -o iclc2023.pdf + +iclc2023.docx: iclc2023.md citations.json + pandoc --citeproc --number-sections iclc2023.md -o iclc2023.docx + +iclc2023x.pdf: iclc2023.md citations.json pandoc/iclc.latex pandoc/iclc.sty + pandoc --template=pandoc/iclc.latex --citeproc --number-sections iclc2023.md --pdf-engine=xelatex -o iclc2023x.pdf diff --git a/paper/README.md b/paper/README.md index 3cac29ce..f93fedfa 100644 --- a/paper/README.md +++ b/paper/README.md @@ -1,8 +1,22 @@ +# paper + +## iclc2023 + +from the strudel project root: + +```sh +npm run iclc +npm run iclc-nocite # try this if you get an error +``` + +## old + Work in progress on a paper about strudel To build you will need -* pandoc - * pandoc-url2cite (`npm install -g pandoc-url2cite`) -* latex/xelatex -* python - * pandocfilters (`pip3 install pandocfilters`) + +- pandoc + - pandoc-url2cite (`npm install -g pandoc-url2cite`) +- latex/xelatex +- python + - pandocfilters (`pip3 install pandocfilters`) diff --git a/paper/citations.json b/paper/citations.json index 1fd923ee..ac34a801 100644 --- a/paper/citations.json +++ b/paper/citations.json @@ -926,5 +926,7 @@ {"id":"zhangDynamicFMRIStudy2006","abstract":"Functional MRI (fMRI) combined with the paired-stimuli paradigms (referred as dynamic fMRI) was used to study the illusory double-flash effect on brain activity in the human visual cortex. Three experiments were designed. The first two experiments aimed to examine the cross-modal neural interaction between the visual and auditory sensory systems caused by the illusory double-flash effect using combined auditory (beep sound) and visual (light flash) stimuli. The fMRI signal in the visual cortex was significantly increased in response to the illusory double flashes compared to the physical single flash when the inter-stimuli delay between the auditory and visual stimuli was 25 ms. This increase disappeared when the delay was prolonged to ∼300 ms. These results reveal that the illusory double-flash effect can significantly affect the brain activity in the visual cortex, and the degree of this effect is dynamically sensitive to the inter-stimuli delay. The third experiment was to address the spatial differentiation of brain activation in the visual cortex in response to the illusory double-flash stimulation. It was found that the illusory double-flash effect in the human visual cortex is much stronger in the periphery than the fovea. This finding suggests that the periphery may be involved in high-level brain processing beyond the retinotopic visual perception. The behavioral measures conducted in this study indicate an excellent correlation between the fMRI results and behavioral performance. Finally, this work demonstrates a unique merit of fMRI for providing both temporal and spatial information regarding cross-modal neural interaction between different sensory systems.","author":[{"family":"Zhang","given":"Nanyin"},{"family":"Chen","given":"Wei"}],"citation-key":"zhangDynamicFMRIStudy2006","container-title":"Experimental Brain Research","DOI":"10.1007/s00221-005-0304-7","ISSN":"0014-4819","issue":"1","issued":{"date-parts":[[2006,6]]},"page":"57–66","title":"A dynamic fMRI study of illusory double-flash effect on human visual cortex","type":"article-journal","URL":"http://dx.doi.org/10.1007/s00221-005-0304-7","volume":"172"}, {"id":"zimmermannMiningVersionHistories2005","abstract":"We apply data mining to version histories in order to guide programmers along related changes: \"Programmers who changed these functions also changed....\" Given a set of existing changes, the mined association rules 1) suggest and predict likely further changes, 2) show up item coupling that is undetectable by program analysis, and 3) can prevent errors due to incomplete changes. After an initial change, our ROSE prototype can correctly predict further locations to be changed; the best predictive power is obtained for changes to existing software. In our evaluation based on the history of eight popular open source projects, ROSE's topmost three suggestions contained a correct location with a likelihood of more than 70 percent.","author":[{"family":"Zimmermann","given":"T."},{"family":"Zeller","given":"A."},{"family":"Weissgerber","given":"P."},{"family":"Diehl","given":"S."}],"citation-key":"zimmermannMiningVersionHistories2005","container-title":"Software Engineering, IEEE Transactions on","container-title-short":"Software Engineering, IEEE Transactions on","DOI":"10.1109/tse.2005.72","ISSN":"0098-5589","issue":"6","issued":{"date-parts":[[2005,6]]},"page":"429–445","title":"Mining version histories to guide software changes","type":"article-journal","URL":"http://dx.doi.org/10.1109/tse.2005.72","volume":"31"}, {"id":"zittrainFutureInternetHow2009","author":[{"family":"Zittrain","given":"Jonathan"}],"citation-key":"zittrainFutureInternetHow2009","ISBN":"0-300-15124-1","issued":{"date-parts":[[2009,3]]},"note":"Published: Paperback","publisher":"Yale University Press","title":"The Future of the Internet–And How to Stop It","type":"book","URL":"http://www.worldcat.org/isbn/0300151241"}, - {"id":"zivanovicDevelopmentCyberneticSculptor2005","abstract":"Edward Ihnatowicz (1926-1988) built one of the world's first computer-controlled robotic sculptures, The Senster, in 1968-70. Rather than concentrate entirely on this groundbreaking and influential piece of work, this paper describes the stages he went through in developing his ideas, as an illustration of how a conventional artist became a cybernetic sculptor.","author":[{"family":"Zivanovic","given":"Aleksandar"}],"citation-key":"zivanovicDevelopmentCyberneticSculptor2005","container-title":"C&C '05: Proceedings of the 5th conference on Creativity & cognition","DOI":"10.1145/1056224.1056240","event-place":"London, United Kingdom","ISBN":"1-59593-025-6","issued":{"date-parts":[[2005]]},"page":"102–108","publisher":"ACM","publisher-place":"London, United Kingdom","title":"The development of a cybernetic sculptor: Edward Ihnatowicz and the senster","type":"paper-conference","URL":"http://dx.doi.org/10.1145/1056224.1056240"} + {"id":"zivanovicDevelopmentCyberneticSculptor2005","abstract":"Edward Ihnatowicz (1926-1988) built one of the world's first computer-controlled robotic sculptures, The Senster, in 1968-70. Rather than concentrate entirely on this groundbreaking and influential piece of work, this paper describes the stages he went through in developing his ideas, as an illustration of how a conventional artist became a cybernetic sculptor.","author":[{"family":"Zivanovic","given":"Aleksandar"}],"citation-key":"zivanovicDevelopmentCyberneticSculptor2005","container-title":"C&C '05: Proceedings of the 5th conference on Creativity & cognition","DOI":"10.1145/1056224.1056240","event-place":"London, United Kingdom","ISBN":"1-59593-025-6","issued":{"date-parts":[[2005]]},"page":"102–108","publisher":"ACM","publisher-place":"London, United Kingdom","title":"The development of a cybernetic sculptor: Edward Ihnatowicz and the senster","type":"paper-conference","URL":"http://dx.doi.org/10.1145/1056224.1056240"}, + {"id":"CsoundWebAssembly","abstract":"This paper describes WebAssembly AudioWorklet (WAAW)\r\nCsound, one of the implementations of Web Audio Csound.\r\nWe begin by introducing the background to this current implementation, stemming from the two first ports of Csound\r\nto the web platform using Native Clients and asm.js. The\r\ntechnology of WebAssembly is then introduced and discussed in its more relevant aspects. The AudioWorklet interface of Web Audio API is explored, together with its use in\r\nWAAW Csound. We complement this discussion by considering the overarching question of support for multiple platforms, which implement different versions of Web Audio.\r\nSome initial examples of the system are presented to illustrate various potential applications. Finally, we complement\r\nthe paper by discussing current issues that are fundamental\r\nfor this project and others that rely on the development of\r\na robust support for WASM-based audio computing.","author":[{"family":"Yi","given":"Steven"},{"family":"Lazzarini","given":"Victor"},{"family":"Costello","given":"Edward"}],"citation-key":"CsoundWebAssembly","event-place":"Berlin, Germany","issued":{"date-parts":[[2018]]},"publisher-place":"Berlin, Germany","title":"WebAssembly AudioWorklet Csound","type":"paper-conference","URL":"https://mural.maynoothuniversity.ie/16018/"}, + {"id":"StrudelWAC2022","publisher":"Zenodo","DOI":"10.5281/zenodo.6768844","language":"eng","title":"Strudel: Algorithmic Patterns for the Web","issued":{"date-parts":[[2022,6,28]]},"abstract":"This paper introduces Strudel (or sometimes StrudelCycles), an alternative implementation of the Tidal (or Tidal-Cycles) live coding system, using the JavaScript programming language. Strudel is an attempt to make live coding more accessible, by creating a system that runs entirely in the browser, while opening Tidals approach to algorithmic patterns (Mclean 2020) up to modern audio/visual web technologies. The Strudel REPL is a live code editor dedicated to manipulating Strudel patterns while they play, with builtin visual feedback. While Strudel is written in JavaScript, the API is optimized for simplicity and readability by applying code transformations on the syntax tree level, allowing language operations that would otherwise be impossible. The application supports multiple ways to output sound, including Tone.js, Web Audio nodes, OSC (Open Sound Control) messages, Web Serial andWeb MIDI. The project is split into multiple packages, allowing granular reuse in other applications. Apart from TidalCycles, Strudel draws inspiration from many prior existing projects like TidalVortex (McLean et al. 2022), Gibber (Roberts and Kuchera-morin 2012), Estuary (Ogborn et al. 2017), Hydra (Jack [2022] 2022), Ocarina (Solomon [2021] 2022) and Feedforward (McLean 2020).","author":[{"family":"Roos", "given":"Felix "},{"family":"McLean","given":"Alex"}],"note":"Demo paper","event-place":"Cannes, France","type":"paper-conference","event":"Web Audio Conference 2022 (WAC 2022)"} ] diff --git a/paper/iclc2023.html b/paper/iclc2023.html new file mode 100644 index 00000000..d2f1fb77 --- /dev/null +++ b/paper/iclc2023.html @@ -0,0 +1,818 @@ + + + + + + + + Strudel: live coding patterns on the Web + + + + + + + +

Abstract

+
+

This paper introduces Strudel, which brings the TidalCycles approach +to live coding algorithmic patterns to native JavaScript and the web. We +begin by giving a little background of the first year of development, +before sharing some detail about its implementation and examples of use. +We go on to outline the wide range of synthesis and other outputs +available in Strudel, including WebAudio, MIDI, OSC (for SuperDirt), +WebSerial and CSound, and introduce Strudel’s REPL live editor, +including its built-in visualisations. We then compare Strudel with +Tidal, the trade-offs involved between JavaScript and Haskell, and the +unique capabilities offered by Strudel for aligning patterns.

+
+ +

1 Introduction

+

In the following paper, we introduce Strudel, an alternative +implementation of the TidalCycles (or ‘Tidal’ for short) live coding +system, using the JavaScript programming language. Strudel is an attempt +to make live coding more accessible, by creating a system that runs +entirely in the browser, while opening Tidal’s approach to algorithmic +patterns (Mclean 2020) up to +modern audio/visual web technologies. The Strudel REPL is a live code +editor dedicated to manipulating patterns while they play, with builtin +visual feedback. While Strudel is written in JavaScript, the API is +optimized for simplicity and readability by applying code +transformations on the syntax tree level, allowing language operations +that would otherwise be impossible. The application supports multiple +ways to output sound, including Tone.js, Web Audio Nodes, OSC (Open +Sound Control) messages, Web Serial, Web MIDI and Csound. The project is +split into multiple packages, allowing granular reuse in other +applications. Apart from TidalCycles, Strudel draws inspiration from +many prior existing projects like TidalVortex (McLean et al. 2022), +Gibber (Roberts and Kuchera-morin +2012), Estuary (Ogborn et al. +2017), Hydra (Jack [2022] 2022), Ocarina (Solomon +[2021] 2022) and Feedforward (McLean 2020). This paper +expands the Strudel Demo paper for the Web Audio Conference 2022 (Roos and McLean +2022).

+

The first tentative commit to the Strudel project was on 22nd January +2022 by Alex McLean, with the core representation implemented over the +following few days. Although this was his first attempt at a +JavaScript-based application, by 27th January, Alex had managed to +upload the initial version to the ‘npm’ javascript package database, +sharing with the wider community for comment. By 4th February, Felix +Roos had discovered Strudel and contributed a ‘REPL’ user interface to +it, and then contributed a scheduler the next day, so that Strudel could +already make sound. At this point, Alex and Felix shared ownership to +the repository, and the project has since proved to be a productive +confluence of Felix’s own work into music representation and +visualisation, with Alex’s experience with making Tidal. Felix has since +become the primary contributor to Strudel, with Alex continuing to jump +between developing both Strudel and Tidal. Aspects of Strudel’s +development have therefore fed back into TidalCycles, and both systems +have maintained a shared conceptual underpinning. We plan to continue +working towards feature parity between these systems, although within +the syntactical trade-offs and library ecosystems of JavaScript and +Haskell, some divergence is inevitable and healthy.

+

Over the first year of its life, Strudel is now a fully-fledged live +coding environment, porting Tidal’s core represention of patterns, +pattern transformations, and mininotation for polymetric sequences, +combined with a wealth of features for synthesising and visualising +those patterns.

+

2 From Tidal to Strudel and +back

+

As mentioned above, the original Tidal is implemented as a domain +specific language (DSL) embedded in the Haskell pure functional +programming language, and takes advantage of Haskell’s terse syntax and +advanced, ‘strong’ type system. JavaScript on the other hand, is a +multi-paradigm programming language, with a dynamic type system. Because +Tidal leans heavily on many of Haskell’s more unique features, it was +not always clear that it could meaningfully be ported to a +multi-paradigm scripting language. However, this possibility was already +demonstrated with an earlier port to Python [TidalVortex; McLean et al. +(2022)], and we have now successfully implemented Tidal’s pure +functional representation of patterns in Strudel, including partial +application, currying, and the functor, applicative and monadic +structures that underlie Tidal’s expressive pattern transformations. The +result is a terse and highly composable system, where everything is +either a pattern, or a function for combining and manipulating patterns, +offering a rich creative ground for exploration.

+

This development process has been far from a one-way port, however. +The process of porting Tidal’s concepts has also opened up new +possibilities, some just from revisiting every design decision, and some +from the particular affordances and constraints offered by JavaScript. +This has lead to new features (and indeed bugfixes) that have found +their way back to Tidal where appropriate, and ongoing work that we will +return to in the conclusion of this paper.

+

3 Representing Patterns

+

Patterns are the essence of Tidal. Its patterns are abstract entities +that represent flows of time as functions, adapting a technique called +pure functional reactive programming. Taking a time span as its input, a +Pattern can output a set of events that happen within that time span. It +depends on the structure of the Pattern how the events are located in +time. From now on, this process of generating events from a time span +will be called querying. Example:

+
const pattern = sequence(c3, [e3, g3])
+const events = pattern.queryArc(0, 1)
+console.log(events.map(e => e.show()))
+

In this example, we create a pattern using the sequence +function and query it for the time span from +0 to 1. Those numbers represent units of time +called cycles. The length of one cycle depends on the +tempo, which defaults to one cycle per second. The resulting events +are:

+
[{ value: 'c3', begin: 0, end: 1/2 },
+{ value: 'e3', begin: 1/2, end: 3/4 },
+{ value: 'g3', begin: 3/4, end: 1 }]
+

Each event has a value, a begin time and an end time, where time is +represented as a fraction. In the above case, the events are placed in +sequential order, where c3 takes the first half, and e3 and g3 together +take the second half. This temporal placement is the result of the +sequence function, which divides its arguments equally over +one cycle. If an argument is an array, the same rule applies to that +part of the cycle. In the example, e3 and g3 are divided equally over +the second half of the whole cycle.

+

The above examples do not represent how Strudel is used in practice. +In the live coding editor, the user only has to type in the pattern +itself, the querying will be handled by the scheduler. The scheduler +will repeatedly query the pattern for events, which are then scheduled +as sound synthesis or other event triggers. Also, the above event data +structure has been simplified for readability.

+
+ + +
+

4 Making Patterns

+

In practice, the end-user live coder will not deal with constructing +patterns directly, but will rather build patterns using Strudel’s +extensive combinator library to create, combine and transform +patterns.

+

The live coder will rarely use the sequence function as +seen above, as sequencing is implicit in many functions. For example in +the following, the note function constructs a pattern of +notes, sequencing its arguments in the same manner as the previous +example.

+
note(c3, [e3, g3])
+

Perhaps more often, they will use the mini-notation for even terser +notation of rhythmic sequences: [^This last example is also valid Tidal +code, albeit the parenthesis is not required in its Haskell syntax in +this case. Tidal does not support passing sequences as lists directly to +the note function, however.].

+
note("c3 [e3 g3]")
+

Such sequences are often treated only a starting point for +manipulation, where they then undergo pattern transformations such as +repetition, symmetry, interference/combination or randomisation, +potentially at multiple timescales. Because Strudel patterns are +represented as pure functions of time rather than as data structures, +very long and complex generative results can be represented and +manipulated without having to store the resulting sequences in +memory.

+

5 Pattern Example

+

The following example showcases how patterns can be utilized to +create musical complexity from simple parts, using repetition and +interference:

+
"<0 2 [4 6](3,4,1) 3>"
+.off(1/4, add(2))
+.off(1/2, add(6))
+.scale('D minor')
+.legato(.25)
+.note().s("sawtooth square")
+.delay(.8).delaytime(.125)
+

The pattern starts with a rhythm of numbers in mini notation, which +are later interpreted inside the scale of D minor. The first line could +also be expressed without mini notation:

+
cat(0, 2, [4, 6].euclid(3, 4, 1), 3)
+

These numbers then undergo various pattern transformations. Here is a +short description of all the functions used:

+ +

Much of the above will be familiar to Tidal users.

+ +

6 Ways to make Sound (and other +events)

+

To generate sound, Strudel supports bindings for different +outputs:

+ +

At first, we used Tone.js as sound output, but it proved to be +limited for the use case of Strudel, where each individual event could +potentially have a completely different audio graph. While the Web Audio +API takes a fire-and-forget approach, creating a lot of Tone.js +instruments and effects causes performance issues quickly. For that +reason, we chose to search for alternatives.

+

Strudel’s new default output uses the Web Audio API to create a new +audio graph for each event. It currently supports basic oscillators, +sample playback, various effects and an experimental support for +soundfonts.

+

WebDirt (Ogborn [2016] 2022) was +created as part of the Estuary Live Coding System (Ogborn et al. +2017), and proved to be a solid choice for handling samples in +Strudel as well. We are however focused on working more directly with +the Web Audio API to be able to integrate new features more tightly.

+

Using the OSC protocol via Strudel’s provided Node.js-based OSC proxy +server, it is possible to send network messages to trigger events. This +is mainly used to render sound using SuperDirt (SuperDirt [2015] 2022), +which is the well-developed Supercollider-based synthesis framework that +Tidal live coders generally use as standard.

+

Recently, the experimental integration of Csound proved to bring a +new dimension of sound design capabilities to Strudel. Thanks to the +WebAssembly distribution of this classic system (Yi, Lazzarini, and Costello +2018), Csound ‘orchestra’ synthesisers can be embedded in and +then patterned with Strudel code.

+

MIDI output can also be used to send MIDI messages to either external +instruments or to other programs on the same device. Unlike OSC, Strudel +is able to send MIDI directly without requiring additional proxy +software, but only from web browsers that support it (at the time of +writing, this means Chromium-based browsers).

+

Finally, Strudel supports Serial output, for example to trigger +events via microcontrollers. This has already been explored for robot +choreography by Kate Sicchio and Alex McLean, via a performance +presented at the International Conference on Live Interfaces 2022.

+

7 The Strudel REPL

+

While Strudel can be used as a library in any JavaScript codebase, +its main, reference user interface is the Strudel REPL[^REPL stands for +read, evaluate, print/play, loop. It is friendly jargon for an +interactive programming interface from computing heritage, usually for a +commandline interface but also applied to live coding editors.], which +is a browser-based live coding environment. This live code editor is +dedicated to manipulating Strudel patterns while they play. The REPL +features built-in visual feedback, which highlights which elements in +the patterned (mini-notation) sequences are influencing the event that +is currently being played. This feedback is designed to support both +learning and live use of Strudel.

+

Besides a UI for playback control and meta information, the main part +of the REPL interface is the code editor powered by CodeMirror. In it, +the user can edit and evaluate pattern code live, using one of the +available synthesis outputs to create music and/or sound art. The +control flow of the REPL follows 3 basic steps:

+
    +
  1. The user writes and updates code. Each update transpiles and +evaluates it to create a Pattern instance
  2. +
  3. While the REPL is running, the Scheduler queries the +active Pattern by a regular interval, generating +Events (also known as Haps in Strudel) for the +next time span.
  4. +
  5. For each scheduling tick, all generated Events are +triggered by calling their onTrigger method, which is set +by the output.
  6. +
+
+REPL control flow + +
+

7.1 User Code

+

To create a Pattern from the user code, two steps are +needed:

+
    +
  1. Transpile the JS input code to make it functional
  2. +
  3. Evaluate the transpiled code
  4. +
+

7.1.1 Transpilation & +Evaluation

+

In the JavaScript world, using transpilation is a common practise to +be able to use language features that are not supported by the base +language. Tools like babel will transpile code that +contains unsupported language features into a version of the code +without those features.

+

In the same tradition, Strudel can add a transpilation step to +simplify the user code in the context of live coding. For example, the +Strudel REPL lets the user create mini notation patterns using just +double quoted strings, while single quoted strings remain what they +are:

+
"c3 [e3 g3]*2"
+

is transpiled to:

+
mini("c3 [e3 g3]*2").withMiniLocation([1,0,0],[1,14,14])
+

Here, the string is wrapped in mini, which will create a +pattern from a mini notation string. Additionally, the +withMiniLocation method passes the original source code +location of the string to the pattern, which enables highlighting active +events.

+

Other convenient features like pseudo variables, operator overloading +and top level await are possible with transpilation.

+

After the transpilation, the code is ready to be evaluated into a +Pattern.

+

Behind the scenes, the user code string is parsed with +acorn, turning it into an Abstract Syntax Tree (AST). The +AST allows changing the structure of the code before generating the +transpiled version using escodegen.

+

7.1.2 Mini Notation

+

While the transpilation allows JavaScript to express Patterns in a +less verbose way, it is still preferable to use the Mini Notation as a +more compact way to express rhythm. Strudel aims to provide the same +Mini Notation features and syntax as used in Tidal.

+

The Mini Notation parser is implemented using peggy, +which allows generating performant parsers for Domain Specific Languages +(DSLs) using a concise grammar notation. The generated parser turns the +Mini Notation string into an AST which is used to call the respective +Strudel functions with the given structure. For example, +"c3 [e3 g3]*2" will result in the following calls:

+
seq(
+  reify('c3').withLocation([1,1,1], [1,4,4]),
+  seq(
+    reify('e3').withLocation([1,5,5], [1,8,8]),
+    reify('g3').withLocation([1,8,8], [1,10,10]),
+  ).fast(2)
+)
+

7.1.3 Highlighting Locations

+

As seen in the examples above, both the JS and the Mini Notation +parser add source code locations using withMiniLocation and +withLocation methods. While the JS parser adds locations +relative to the user code as a whole, the Mini Notation adds locations +relative to the position of the mini notation string. The absolute +location of elements within Mini Notation can be calculated by simply +adding both locations together. This absolute location can be used to +highlight active events in real time.

+

7.2 Scheduling Events

+

After an instance of Pattern is obtained from the user +code, it is used by the scheduler to get queried for events. Once +started, the scheduler runs at a fixed interval to query active pattern +for events withing the current interval’s time span. A simplified +implementation looks like this:

+
let pattern = seq('c3', ['e3', 'g3']); // pattern from user
+let interval = 0.5; // query interval in seconds
+let time = 0; // beginning of current time span
+let minLatency = .1; // min time before a hap should trigger
+setInterval(() => {
+  const haps = pattern.queryArc(time, time + interval);
+  time += interval; // increment time
+  haps.forEach((hap) => {
+    const deadline = hap.whole.begin - time + minLatency;
+    onTrigger(hap, deadline, duration);
+  });
+}, interval * 1000); // query each "interval" seconds
+

Note that the above code is simplified for illustrative purposes. The +actual implementation has to work around imprecise callbacks of +setInterval. More about the implementation details can be +read in this +blog post.

+

The fact that Pattern.queryArc is a pure function that +maps a time span to a set of events allows us to choose any interval we +like without changing the resulting output. It also means that when the +pattern is changed from outside, the next scheduling callback will work +with the new pattern, keeping its clock running.

+

The latency between the time the pattern is evaluated and the change +is heard is between minLatency and +interval + minLatency, in our example between 100ms and +600ms. In Strudel, the current query interval is 50ms with a minLatency +of 100ms, meaning the latency is between 50ms and 150ms.

+

7.3 Output

+

The last step is to trigger each event in the chosen output. This is +where the given time and value of each event is used to generate audio +or any other form of time based output. The default output of the +Strudel REPL is the WebAudio output. To understand what an output does, +we first have to understand what control parameters are.

+

7.3.1 Control Parameters

+

To be able to manipulate multiple aspects of sound in parallel, so +called control parameters are used to shape the value of each event. +Example:

+
note("c3 e3").cutoff(1000).s('sawtooth')
+  .queryArc(0, 1).map(hap => hap.value)
+/* [
+  { note: 'c3', cutoff: 1000, s: 'sawtooth' }
+  { note: 'e3', cutoff: 1000, s: 'sawtooth' }
+] */
+

Here, the control parameter functions note, +cutoff and s are used, where each controls a +different property in the value object. Each control parameter function +accepts a primitive value, a list of values to be sequenced into a +Pattern, or a Pattern. In the example, +note gets a Pattern from a Mini Notation +expression (double quoted), while cutoff and s +are given a Number and a (single quoted) +String respectively.

+

Strudel comes with a large default set of control parameter functions +that are based on the ones used by Tidal and SuperDirt, focusing on +music and audio terminology. It is however possible to create custom +control paramters for any purpose:

+
const { x, y } = createParams('x', 'y')
+x(sine.range(0, 200)).y(cosine.range(0,200))
+

This example creates the custom control parameters x and +y which are then used to form a pattern that descibes the +coordinates of a circle.

+

7.3.2 Outputs

+

Now that we know how the value of an event is manipulated using +control parameters, we can look at how outputs can use that value to +generate anything. The scheduler above was calling the +onTrigger function which is used to implement the output. A +very simple version of the web audio output could look like this:

+
function onTrigger(hap, deadline, duration) {
+  const { note } = hap.value;
+  const time = getAudioContext().currentTime + deadline;
+  const o = getAudioContext().createOscillator();
+  o.frequency.value = getFreq(note);
+  o.start(time);
+  o.stop(time + event.duration);
+  o.connect(getAudioContext().destination);
+}
+

The above example will create an OscillatorNode for each +event, where the frequency is controlled by the note param. +In essence, this is how the WebAudio API output of Strudel works, only +with many more parameters to control synths, samples and effects.

+

8 Pattern alignment and +combination

+

One core aspect of Strudel, inherited from Tidal, is the flexible way +that patterns can be combined, irrespective of their structure. Its +declarative approach means a live coder does not have to think about the +details of how this is done, only what is to be +done.

+

As a simple example, consider two number patterns +"0 [1 2] 3", and "10 20". The first has three +contiguous steps of equal lengths, with the second step broken down into +two substeps, giving four events in total. There are a very large number +of ways in which the structure of these two patterns could be combined, +but the default method in both Strudel and Tidal is to line up the +cycles of the two patterns, and then take events from the first pattern +and match them with those in the second pattern. Therefore, the +following two lines are equivalent:

+
"0 [1 2] 3".add("10 20")
+"10 [11 22] 23"
+

Where the events only partially overlap, they are treated as +fragments of the event in the first pattern. This is a little difficult +to conceptualise, but lets start by comparing the two patterns in the +following example:

+
"0 1 2".add("10 20")
+"10 [11 21] 20"
+

They are similar to the previous example in that the number +1 is split in two, with its two halves added to +10 and 20 respectively. However, the +11 ‘remembers’ that it is a fragment of that original +1 event, and so is treated as having a duration of a third +of a cycle, despite only being active for a sixth of a cycle. Likewise, +the 21 is also a fragment of that original 1 +event, but a fragment of its second half. Because the start of its event +is missing, it wouldn’t actually trigger a sound (unless it underwent +further pattern transformations/combinations).

+

In practice, the effect of this default, implicit method for +combining two patterns is that the second pattern is added in +to the first one, and indeed this can be made explicit:

+
"0 1 2".add.in("10 20")
+

This makes way for other ways to align the pattern, and several are +already defined, in particular:

+ +

We will save going deeper into the background, design and +practicalities of these alignment functions for future publications. +However in the next section, we take them as a case study for looking at +the different design affordances offered by Haskell to Tidal, and +JavaScript to Strudel.

+

9 Comparing Strudel and Haskell in +use

+

Unlike Haskell, JavaScript lacks the ability to define custom infix +operators, or change the meaning of existing ones. So the above Strudel +example of "0 1 2".add.out("10 20") is equivalent to the +Tidal expression "0 1 2" +| "10 20", where the vertical bar +in the operator +| stands for out (where +a |+ b would be equivalent of +a.add.in(b)).

+

From this we can already see that Tidal tends towards brevity through +mixing infix operators with functions, and Strudel tends towards +spelling out operations which are joined together with the +. operator. This then is the design trade-off of Tidal’s +tersity, versus Strudel’s simplicity.

+

To demonstrate this, consider the following Tidal pattern:

+
iter 4 $ every 3 (||+ n "10 20") $ (n "0 1 3") # s "triangle" # crush 4
+

This can be directly translated to the Strudel equivalent:

+
iter(4, every(3, add.squeeze("10 20"), n("0 1 3").s("triangle").crush(4)))
+

Although for a more canonical Strudel expression, we would reorder it +as:

+
n("0 1 3").every(3, add.squeeze("10 20")).iter(4).s("triangle").crush(4)
+

The Strudel example uses the . method call operator for +all operations and combinations, whereas the Tidal example has +# for the default method for combining patterns and uses +infix operators for other methods. The lack of parenthesis in the Tidal +example is partly due to the way that arguments are applied to Haskell’s +functions, and partly due to the use of the $ operator as +an alternative way to establish precedence and control the order of +evaluation.

+

Considering the above, we argue that the Haskell syntax is a little +cleaner, but that the Strudel syntax is easier to learn. Our informal +observation is that while Haskell’s dollar $ operator is +very useful in making code easier to work with, it is one of the most +difficult aspects of Tidal use for beginners to learn. On the other +hand, the deeper levels of parenthesis in Strudel code can be difficult +to keep track of, especially while coding under pressure of live musical +performance. However this difficulty can be largely be mitigated by +reordering expressions, and further mitigated by supporting editor +features.

+

With Strudel, we have little choice but to embrace the affordances +and constraints offered by JavaScript, and while designing a +domain-specific language entirely based on method calls is a challenge, +through creative adoption of functional programming techniques like +partial application, we are so far very happy with the results. Tidal’s +functional reactive approach to pattern-making has in general translated +well to JavaScript, and opportunities and constraints have overall +traded off to create a very approachable and useable live coding +environment.

+

9.1 The trade-off of flexible +typing

+

We have identified one problem with porting Tidal to JavaScript where +we have missed Haskell’s strict typing and type inference. In both Tidal +and Strudel, time is rational, where any point in time is represented as +the ratio of two integers. This allows representation of musical ratios +such that are impossible to represent accurately using the more common +floating point numbers. However while libraries are available that +support rational numbers in JavaScript, the lack of strict typing means +that it is easy to implement pattern methods where computationally +expensive conversion from floating point to rational numbers are +performed late, and therefore often enough to overload the CPUs, due to +the large number of iterative calculations required to estimate a ratio +for a given floating point number. To mitigate this problem, we might +consider moving to TypeScript in the future.

+

10 Future Outlook

+

The project is still young, with many features on the horizon. As +general guiding principles, Strudel aims to be

+
    +
  1. accessible
  2. +
  3. consistent with Tidal’s approach to pattern
  4. +
  5. modular and extensible
  6. +
+

While Haskell’s type system makes it a great language for the ongoing +development of Tidal’s inner representation of pattern, JavaScript’s +vibrant ecosystem, flexibility and accessibility makes it a great host +for more ad-hoc experiments, including interface design. For the future, +it is planned to integrate additional alternative sound engines such as +Glicol (Lan +[2020] 2022) and Faust (Faust - Programming +Language for Audio Applications and Plugins [2016] 2022). +Strudel is already approaching feature parity with Tidal, but there are +more Tidal functions to be ported, and work to be done to improve +compatibility with Tidal’s mininotation. Tidal version 2.0 is under +development, which brings a new representation for sequences to its +patterns, which will then be brought to Strudel. Besides sound, other +ways to render events are being explored, such as graphical, and +choreographic output. We are also looking into alternative ways of +editing patterns, including multi-user editing for network music, +parsing a novel syntax to escape the constraints of javascript, and +developing hardware/e-textile interfaces. In summary, there is a lot of +fun ahead.

+

11 Links

+

The Strudel REPL is available at https://strudel.tidalcycles.org, including an +interactive tutorial. The repository is at https://github.com/tidalcycles/strudel, all the code is +open source under the AGPL-3.0 License.

+

12 Acknowledgments

+

Thanks to the Strudel and wider Tidal, live coding, WebAudio and +free/open source software communities for inspiration and support. Alex +McLean’s work on this project is supported by a UKRI Future Leaders +Fellowship [grant number MR/V025260/1].

+

References

+
+
+Faust - Programming Language for Audio Applications and +Plugins. (2016) 2022. C++. GRAME. https://github.com/grame-cncm/faust. +
+
+Jack, Olivia. (2022) 2022. Hydra. https://github.com/ojack/hydra. +
+
+Lan, Qichao. (2020) 2022. Chaosprint/Glicol. Rust. https://github.com/chaosprint/glicol. +
+
+Mclean, Alex. 2020. “Algorithmic Pattern.” In +Proceedings of the International Conference on New Interfaces for +Musical Expression, 265--270. Birmingham, UK. https://zenodo.org/record/4813352. +
+
+McLean, Alex. 2020. “Feedforward.” In Proceedings of +New Interfaces for Musical Expression. Birmingham. https://zenodo.org/record/6353969. +
+
+McLean, Alex, Raphaël Forment, Sylvain Le Beux, and Damián Silvani. +2022. “TidalVortex Zero.” In Proceedings of the 7th +International Conference on Live Coding. Limerick, Ireland: Zenodo. +https://doi.org/10.5281/zenodo.6456380. +
+
+Ogborn, David. (2016) 2022. Dktr0/WebDirt. JavaScript. https://github.com/dktr0/WebDirt. +
+
+Ogborn, David, Jamie Beverley, Luis Navarro del Angel, Eldad Tsabary, +and Alex McLean. 2017. “Estuary: Browser-Based Collaborative +Projectional Live Coding of Musical Patterns.” In Proceedings +of the International Conference on Live Coding, 11. Morelia. +
+
+Roberts, Charles, and Joann Kuchera-morin. 2012. “Gibber: Live +Coding Audio in the Browser.” In In Proceedings of the 2012 +International Computer Music Conference. +
+
+Roos, Felix, and Alex McLean. 2022. “Strudel: Algorithmic Patterns +for the Web.” In. Zenodo. https://doi.org/10.5281/zenodo.6768844. +
+
+Solomon, Mike. (2021) 2022. Purescript-Ocarina. PureScript. https://github.com/mikesol/purescript-ocarina. +
+
+SuperDirt. (2015) 2022. SuperCollider. musikinformatik. https://github.com/musikinformatik/SuperDirt. +
+
+Toussaint, Godfried. 2005. “The Euclidean Algorithm Generates +Traditional Musical Rhythms.” In In Proceedings of BRIDGES: +Mathematical Connections in Art, Music and Science, 47–56. http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.62.231. +
+
+Yi, Steven, Victor Lazzarini, and Edward Costello. 2018. +“WebAssembly AudioWorklet Csound.” In. Berlin, Germany. https://mural.maynoothuniversity.ie/16018/. +
+
+ + diff --git a/paper/iclc2023.md b/paper/iclc2023.md new file mode 100644 index 00000000..ac744bb5 --- /dev/null +++ b/paper/iclc2023.md @@ -0,0 +1,460 @@ +--- +title: 'Strudel: live coding patterns on the Web' +author: + - name: Felix Roos + affiliation: Unaffiliated + email: flix91@gmail.com + - name: Alex McLean + affiliation: Then Try This + email: alex@slab.org +abstract: | + This paper introduces Strudel, which brings the TidalCycles approach to live coding algorithmic patterns to native JavaScript and the web. We begin by giving a little background of the first year of development, before sharing some detail about its implementation and examples of use. We go on to outline the wide range of synthesis and other outputs available in Strudel, including WebAudio, MIDI, OSC (for SuperDirt), WebSerial and CSound, and introduce Strudel's REPL live editor, including its built-in visualisations. We then compare Strudel with Tidal, the trade-offs involved between JavaScript and Haskell, and the unique capabilities offered by Strudel for aligning patterns, before concluding with some thoughts about the future. +bibliography: citations.json +fontsize: 11pt +geometry: margin=2cm +fontfamily: libertine +fontfamily: inconsolata +mainfont: Linux Libertine O +monofont: Inconsolata +date: '2022-12-14' +--- + +# Introduction + +In the following paper, we introduce *Strudel*, an alternative +implementation of the TidalCycles (or 'Tidal' for short) live coding +system, using the JavaScript programming language. Strudel is an +attempt to make live coding more accessible, by creating a system that +runs entirely in the browser, while opening Tidal's approach to +algorithmic patterns [@mcleanAlgorithmicPattern2020a] up to modern +audio/visual web technologies. The Strudel REPL is a live code editor +dedicated to manipulating patterns while they play, with builtin +visual feedback. While Strudel is written in JavaScript, the API is +optimized for simplicity and readability by applying code +transformations on the syntax tree level, allowing language operations +that would otherwise be impossible. The application supports multiple +ways to output sound, including Tone.js, Web Audio Nodes, OSC (Open +Sound Control) messages, Web Serial, Web MIDI and Csound. The project +is split into multiple packages, allowing granular reuse in other +applications. Apart from TidalCycles, Strudel draws inspiration from +many prior existing projects like TidalVortex +[@mcleanTidalVortexZero2022], Gibber [@robertsGibberLiveCoding2012], +Estuary [@ogbornEstuaryBrowserbasedCollaborative2017], Hydra +[@jackHydra2022], Ocarina [@solomonPurescriptocarina2022] and +Feedforward [@mcleanFeedforward2020]. This paper expands the Strudel +Demo paper for the Web Audio Conference 2022 [@StrudelWAC2022]. + +The first tentative commit to the Strudel project was on 22nd January +2022 by Alex McLean, with the core representation implemented over the +following few days. Although this was his first attempt at a +JavaScript-based application, by 27th January, Alex had managed to +upload the initial version to the 'npm' javascript package database, +sharing with the wider community for comment. By 4th February, Felix +Roos had discovered Strudel and contributed a 'REPL' user interface to +it, and then contributed a scheduler the next day, so that Strudel +could already make sound. At this point, Alex and Felix shared +ownership to the repository, and the project has since proved to be a +productive confluence of Felix's own work into music representation +and visualisation, with Alex's experience with making Tidal. Felix has +since become the primary contributor to Strudel, with Alex continuing +to jump between developing both Strudel and Tidal. Aspects of +Strudel's development have therefore fed back into TidalCycles, and +both systems have maintained a shared conceptual underpinning. We plan +to continue working towards feature parity between these systems, +although within the syntactical trade-offs and library ecosystems of +JavaScript and Haskell, some divergence is inevitable and healthy. + +Over the first year of its life, Strudel is now a fully-fledged live +coding environment, porting Tidal's core represention of patterns, +pattern transformations, and mini-notation for polymetric sequences, +combined with a wealth of features for synthesising and visualising +those patterns. + +# From Tidal to Strudel and back + +As mentioned above, the original Tidal is implemented as a domain specific language (DSL) embedded in the Haskell pure functional programming language, and takes advantage of Haskell's terse syntax and advanced, 'strong' type system. JavaScript on the other hand, is a multi-paradigm programming language, with a dynamic type system. Because Tidal leans heavily on many of Haskell's more unique features, it was not always clear that it could meaningfully be ported to a multi-paradigm scripting language. However, this possibility was already demonstrated with an earlier port to Python [TidalVortex; @mcleanTidalVortexZero2022], and we have now successfully implemented Tidal's pure functional representation of patterns in Strudel, including partial application, currying, and the functor, applicative and monadic structures that underlie Tidal's expressive pattern transformations. The result is a terse and highly composable system, where everything is either a pattern, or a function for combining and manipulating patterns, offering a rich creative ground for exploration. + +This development process has been far from a one-way port, however. The process of porting Tidal's concepts has also opened up new possibilities, some just from revisiting every design decision, and some from the particular affordances and constraints offered by JavaScript. This has lead to new features (and indeed bugfixes) that have found their way back to Tidal where appropriate, and ongoing work that we will return to in the conclusion of this paper. + +# Representing Patterns + +Patterns are the essence of Tidal. Its patterns are abstract entities that represent flows of time as functions, adapting a technique called pure functional reactive programming. Taking a time span as its input, a Pattern can output a set of events that happen within that time span. It depends on the structure of the Pattern how the events are located in time. +From now on, this process of generating events from a time span will be called **querying**. +Example: + +```js +const pattern = sequence(c3, [e3, g3]) +const events = pattern.queryArc(0, 1) +console.log(events.map(e => e.show())) +``` + +In this example, we create a pattern using the `sequence` function and **query** it for the time span from `0` to `1`. +Those numbers represent units of time called **cycles**. The length of one cycle depends on the tempo, which defaults to one cycle per second. +The resulting events are: + +```js + ["[ 0/1 -> 1/2 | c3 ]", + "[ 1/2 -> 3/4 | e3 ]", + "[ 3/4 -> 1/1 | g3 ]" + ] +``` + +Each event has a value, a begin time and an end time, where time is represented as a fraction. In the above case, the events are placed in sequential order, where c3 takes the first half, and e3 and g3 together take the second half. This temporal placement is the result of the `sequence` function, which divides its arguments equally over one cycle. If an argument is an array, the same rule applies to that part of the cycle. In the example, e3 and g3 are divided equally over the second half of the whole cycle. + +Note that the query function is not just a way to access a pattern, but true to the principles of functional programming, is the pattern itself. This means that in theory there is no way to change a pattern, it is opaque as a pure function. In practice though, Strudel and Tidal are all about transforming patterns, so how is this done? The answer is, by replacing the pattern with a new one, that calls the old one. This new one is only able to manipulate the query before passing it to the old pattern, and manipulate the results from it before returning them to caller. But, this is enough to support all the temporal and structural manipulations provided by Strudel (and Tidal's) extensive library of functions. + +The above examples do not represent how Strudel is used in practice. In the live coding editor, the user only has to type in the pattern itself, the querying will be handled by the scheduler. The scheduler will repeatedly query the pattern for events, which are then scheduled as sound synthesis or other event triggers. +Also, the above event data structure has been simplified for readability. + +![Screenshot of the Strudel 'REPL' live coding editor, including piano-roll visualisation.](images/strudel-screenshot2.png){ width=60% } + +# Making Patterns + +In practice, the end-user live coder will not deal with constructing patterns directly, but will rather build patterns using Strudel's extensive combinator library to create, combine and transform patterns. + +The live coder will rarely use the `sequence` function as seen above, as sequencing is implicit in many functions. For example in the following, the `note` function constructs a pattern of notes, sequencing its arguments in the same manner as the previous example. + +```js +note(c3, [e3, g3]) +``` + +Perhaps more often, they will use the mini-notation for even terser notation of rhythmic sequences: ^[This last example is also valid Tidal code, albeit the parenthesis is not required in its Haskell syntax in this case. Tidal does not support passing sequences as lists directly to the `note` function, however.]. + +```js +note("c3 [e3 g3]") +``` + +Such sequences are often treated only as a starting point for manipulation, where they then undergo pattern transformations such as repetition, symmetry, interference/combination or randomisation, potentially at multiple timescales. Because Strudel patterns are represented as pure functions of time rather than as data structures, very long and complex generative results can be represented and manipulated without having to store the resulting sequences in memory. + +# Pattern Example + +The following example showcases how patterns can be utilized to create musical complexity from simple parts, using repetition and interference: + +```js +"<0 2 [4 6](3,4,1) 3>" +.off(1/4, add(2)) +.off(1/2, add(6)) +.scale('D minor') +.legato(.25) +.note().s("sawtooth square") +.delay(.8).delaytime(.125) +``` + +The pattern starts with a rhythm of numbers in mini-notation, which are later interpreted inside the scale of D minor. +The first line could also be expressed without mini-notation: + +```js +cat(0, 2, [4, 6].euclid(3, 4, 1), 3) +``` + +These numbers then undergo various pattern transformations. Here is a short description of all the functions used: + +- `cat`: play elements sequentially, where each lasts one cycle +- `brackets`: elements inside brackets are divided equally over the time of their parent +- `.euclid(p, s, o)`: place p pulses evenly over s steps, with offset o [@toussaintEuclideanAlgorithmGenerates2005] +- `.off(n, f)`: layers a pattern on top of itself, with the new layer offset by n cycles, and with function f applied +- `.legato(n)`: multiply the duration of all events in a pattern by a factor of n +- `.echo(t, n, v)`: copy each event t times, with n cycles in between each copy, decreasing velocity by v +- `.note()`: interpretes values as notes +- `.s(name)`: play back each event with the given sound +- `.delay(wet)`: add delay +- `.delaytime(t)`: set delay time + +Much of the above will be familiar to Tidal users. + + + +# Ways to make Sound (and other events) + +To generate sound, Strudel supports bindings for different outputs: + +- Tone.js (deprecated) +- Web Audio API +- WebDirt, a js recreation of Tidal's *Dirt* sample engine (deprecated) +- OSC via osc-js, compatible with superdirt +- Csound via the Csound WebAssembly build +- MIDI via WebMIDI +- Serial via WebSerial + +At first, we used Tone.js as sound output, but it proved to be limited for the use case of Strudel, where each individual event could potentially have a completely different audio graph. +While the Web Audio API takes a *fire-and-forget* approach, creating a lot of Tone.js instruments and effects causes performance issues quickly. For that reason, we chose to search for alternatives. + +Strudel's new default output uses the Web Audio API to create a new audio graph for each event. It currently supports basic oscillators, sample playback, various effects and an experimental support for soundfonts. + +WebDirt [@ogbornDktr0WebDirt2022] was created as part of the Estuary Live Coding System [@ogbornEstuaryBrowserbasedCollaborative2017], and proved to be a solid choice for handling samples in Strudel as well. We are however focused on working more directly with the Web Audio API to be able to integrate new features more tightly. + +Using the OSC protocol via Strudel's provided Node.js-based OSC proxy server, it is possible to send network messages to trigger events. This is mainly used to render sound using SuperDirt [@SuperDirt2022], which is the well-developed Supercollider-based synthesis framework that Tidal live coders generally use as standard. + +Recently, the experimental integration of Csound proved to bring a new dimension of sound design capabilities to Strudel. Thanks to the WebAssembly distribution of this classic system [@CsoundWebAssembly], Csound 'orchestra' synthesisers can be embedded in and then patterned with Strudel code. + +MIDI output can also be used to send MIDI messages to either external instruments or to other programs on the same device. Unlike OSC, Strudel is able to send MIDI directly without requiring additional proxy software, but only from web browsers that support it (at the time of writing, this means Chromium-based browsers). + +Finally, Strudel supports Serial output, for example to trigger events +via microcontrollers. This has already been explored for robot +choreography by Kate Sicchio and Alex McLean, via a performance +presented at the International Conference on Live Interfaces 2022. + +# The Strudel REPL + +While Strudel can be used as a library in any JavaScript codebase, its main, reference user interface is the Strudel REPL^[REPL stands for read, evaluate, print/play, loop. It is friendly jargon for an interactive programming interface from computing heritage, usually for a commandline interface but also applied to live coding editors.], which is a browser-based live coding environment. This live code editor is dedicated to manipulating Strudel patterns while they play. The REPL features built-in visual feedback, highlighting which elements in the patterned (mini-notation) sequences are influencing the event that is currently being played. This feedback is designed to support both learning and live use of Strudel. + +Besides a UI for playback control and meta information, the main part of the REPL interface is the code editor powered by CodeMirror. In it, the user can edit and evaluate pattern code live, using one of the available synthesis outputs to create music and/or sound art. The control flow of the REPL follows 3 basic steps: + +1. The user writes and updates code. Each update transpiles and evaluates it to create a `Pattern` instance +2. While the REPL is running, the `Scheduler` queries the active `Pattern` by a regular interval, generating `Events` (also known as `Haps` in Strudel) for the next time span. +3. For each scheduling tick, all generated `Events` are triggered by calling their `onTrigger` method, which is set by the output. + +![REPL control flow](images/strudelflow.png){ width=43% } + +## User Code + +To create a `Pattern` from the user code, two steps are needed: + +1. Transpile the JS input code to make it functional +2. Evaluate the transpiled code + +### Transpilation & Evaluation + +In the JavaScript world, using transpilation is a common practise to be able to use language features that are not supported by the base language. Tools like `babel` will transpile code that contains unsupported language features into a version of the code without those features. + +In the same tradition, Strudel can add a transpilation step to simplify the user code in the context of live coding. For example, the Strudel REPL lets the user create mini-notation patterns using just double quoted strings, while single quoted strings remain what they are: + +```js +"c3 [e3 g3]*2" +``` + +is transpiled to: + +```js +mini("c3 [e3 g3]*2").withMiniLocation([1,0,0],[1,14,14]) +``` + +Here, the string is wrapped in `mini`, which will create a pattern from a mini-notation string. Additionally, the `withMiniLocation` method passes the original source code location of the string to the pattern, which enables highlighting active events. + +Other convenient features like pseudo variables, operator overloading and top level await are possible with transpilation. + +After the transpilation, the code is ready to be evaluated into a `Pattern`. + +Behind the scenes, the user code string is parsed with `acorn`, turning it into an Abstract Syntax Tree (AST). The AST allows changing the structure of the code before generating the transpiled version using `escodegen`. + +### Mini-notation + +While the transpilation allows JavaScript to express Patterns in a less verbose way, it is still preferable to use the mini-notation as a more compact way to express rhythm. Strudel aims to provide the same mini-notation features and syntax as used in Tidal. + +The mini-notation parser is implemented using `peggy`, which allows generating performant parsers for Domain Specific Languages (DSLs) using a concise grammar notation. The generated parser turns the mini-notation string into an AST which is used to call the respective Strudel functions with the given structure. For example, `"c3 [e3 g3]*2"` will result in the following calls: + +```js +seq( + reify('c3').withLocation([1,1,1], [1,4,4]), + seq( + reify('e3').withLocation([1,5,5], [1,8,8]), + reify('g3').withLocation([1,8,8], [1,10,10]), + ).fast(2) +) +``` + +### Highlighting Locations + +As seen in the examples above, both the JS and the mini-notation parser add source code locations using `withMiniLocation` and `withLocation` methods. While the JS parser adds locations relative to the user code as a whole, the mini-notation adds locations relative to the position of the mini-notation string. The absolute location of elements within mini-notation can be calculated by simply adding both locations together. This absolute location can be used to highlight active events in real time. + +## Scheduling Events + +After an instance of `Pattern` is obtained from the user code, +it is used by the scheduler to get queried for events. Once started, the scheduler runs at a fixed interval to query the active pattern for events within the current interval's time span. A simplified implementation looks like this: + +```js +let pattern = seq('c3', ['e3', 'g3']); // pattern from user +let interval = 0.5; // query interval in seconds +let time = 0; // beginning of current time span +let minLatency = .1; // min time before a hap should trigger +setInterval(() => { + const haps = pattern.queryArc(time, time + interval); + time += interval; // increment time + haps.forEach((hap) => { + const deadline = hap.whole.begin - time + minLatency; + onTrigger(hap, deadline, duration); + }); +}, interval * 1000); // query each "interval" seconds +``` + +Note that the above code is simplified for illustrative purposes. The actual implementation has to work around imprecise callbacks of `setInterval`. More about the implementation details can be read in [this blog post](https://loophole-letters.vercel.app/web-audio-scheduling). + +The fact that `Pattern.queryArc` is a pure function that maps a time span to a set of events allows us to choose any interval we like without changing the resulting output. It also means that when the pattern is changed from outside, the next scheduling callback will work with the new pattern, keeping its clock running. + +The latency between the time the pattern is evaluated and the change is heard is between `minLatency` and `interval + minLatency`, in our example between 100ms and 600ms. In Strudel, the current query interval is 50ms with a minLatency of 100ms, meaning the latency is between 50ms and 150ms. + +## Output + +The last step is to trigger each event in the chosen output. +This is where the given time and value of each event is used to generate audio or any other form of time based output. The default output of the Strudel REPL is the WebAudio output. To understand what an output does, we first have to understand what control parameters are. + +### Control Parameters + +To be able to manipulate multiple aspects of sound in parallel, so called control parameters are used to shape the value of each event. Example: + +```js +note("c3 e3").cutoff(1000).s('sawtooth') + .queryArc(0, 1).map(hap => hap.value) +/* [ + { note: 'c3', cutoff: 1000, s: 'sawtooth' } + { note: 'e3', cutoff: 1000, s: 'sawtooth' } +] */ +``` + +Here, the control parameter functions `note`, `cutoff` and `s` are used, where each controls a different property in the value object. Each control parameter function accepts a primitive value, a list of values to be sequenced into a `Pattern`, or a `Pattern`. In the example, `note` gets a `Pattern` from a mini-notation expression (double quoted), while `cutoff` and `s` are given a `Number` and a (single quoted) `String` respectively. + +Strudel comes with a large default set of control parameter functions that are based on the ones used by Tidal and SuperDirt, focusing on music and audio terminology. It is however possible to create custom control parameters for any purpose: + +```js +const { x, y } = createParams('x', 'y') +x(sine.range(0, 200)).y(cosine.range(0,200)) +``` + +This example creates the custom control parameters `x` and `y` which are then used to form a pattern that descibes the coordinates of a circle. + +### Outputs + +Now that we know how the value of an event is manipulated using control parameters, we can look at how outputs can use that value to generate anything. The scheduler above was calling the `onTrigger` function which is used to implement the output. A very simple version of the web audio output could look like this: + +```js +function onTrigger(hap, deadline, duration) { + const { note } = hap.value; + const time = getAudioContext().currentTime + deadline; + const o = getAudioContext().createOscillator(); + o.frequency.value = getFreq(note); + o.start(time); + o.stop(time + event.duration); + o.connect(getAudioContext().destination); +} +``` + +The above example will create an `OscillatorNode` for each event, where the frequency is controlled by the `note` param. In essence, this is how the WebAudio API output of Strudel works, only with many more parameters to control synths, samples and effects. + +# Pattern alignment and combination + +One core aspect of Strudel, inherited from Tidal, is the flexible way that patterns can be combined, irrespective of their structure. Its declarative approach means a live coder does not have to think about the details of *how* this is done, only *what* is to be done. + +As a simple example, consider two number patterns `"0 [1 2] 3"`, and `"10 20"`. The first has three contiguous steps of equal lengths, with the second step broken down into two substeps, giving four events in total. There are a very large number of ways in which the structure of these two patterns could be combined, but the default method in both Strudel and Tidal is to line up the cycles of the two patterns, and then take events from the first pattern and match them with those in the second pattern. Therefore, the following two lines are equivalent: + +```js +"0 [1 2] 3".add("10 20") +"10 [11 22] 23" +``` + +Where the events only partially overlap, they are treated as fragments +of the event in the first pattern. This is a little difficult to +conceptualise, but lets start by comparing the two patterns in the +following example: + +```js +"0 1 2".add("10 20") +"10 [11 21] 20" +``` + +They are similar to the previous example in that the number `1` is split in two, with its two halves added to `10` and `20` respectively. However, the `11` 'remembers' that it is a fragment of that original `1` event, and so is treated as having a duration of a third of a cycle, despite only being active for a sixth of a cycle. Likewise, the `21` is also a fragment of that original `1` event, but a fragment of its second half. Because the start of its event is missing, it wouldn't actually trigger a sound (unless it underwent further pattern transformations/combinations). + +In practice, the effect of this default, implicit method for combining two patterns is that the second pattern is added *in* to the first one, and indeed this can be made explicit: + +```js +"0 1 2".add.in("10 20") +``` + +This makes way for other ways to align the pattern, and several are already defined, in particular: + +* `in` - as explained above, aligns cycles, and applies values from the pattern on the right *in* to the pattern on the left. +* `out` - as with `in`, but values are applied *out* of the pattern on the left (i.e. *in* to the one on the right). +* `mix` - structures from both patterns are combined, so that the new events are not fragments but are created at intersections of events from both sides. +* `squeeze` - cycles from the pattern on the right are squeezed into events on the left. So that e.g. `"0 1 2".add.squeeze("10 20")` is equivalent to `"[10 20] [11 21] [12 22]"`. +* `squeezeout` - as with `squeeze`, but cycles from the left are squeezed into events on the right. So, `"0 1 2".add.squeezeout("10 20")` is equivalent to `[10 11 12] [20 21 22]`. +* `trig` is similar to `squeezeout` in that cycles from the right are aligned with events on the left. However those cycles are not 'squeezed', rather they are truncated to fit the event. So `"0 1 2 3 4 5 6 7".add.trig("10 [20 30]")` would be equivalent to `10 11 12 13 20 21 30 31`. In effect, events on the right 'trigger' cycles on the left. +* `trigzero` is similar to `trig`, but the pattern is 'triggered' from its very first cycle, rather than from the current cycle. `trig` and `trigzero` therefore only give different results where the leftmost pattern differs from one cycle to the next. + +We will save going deeper into the background, design and practicalities of these alignment functions for future publications. However in the next section, we take them as a case study for looking at the different design affordances offered by Haskell to Tidal, and JavaScript to Strudel. + +# Comparing Strudel and Haskell in use + +Unlike Haskell, JavaScript lacks the ability to define custom infix +operators, or change the meaning of existing ones. So the above +Strudel example of `"0 1 2".add.out("10 20")` is equivalent to the +Tidal expression `"0 1 2" +| "10 20"`, where the vertical bar in the +operator `+|` stands for `out` (where `a |+ b` would be equivalent of +`a.add.in(b)`). + +From this we can already see that Tidal tends towards brevity through +mixing infix operators with functions, and Strudel tends towards +spelling out operations which are joined together with the `.` +operator. This then is the design trade-off of Tidal's tersity, +versus Strudel's simplicity. + +To demonstrate this, consider the following Tidal pattern: + +```haskell +iter 4 $ every 3 (||+ n "10 20") $ (n "0 1 3") # s "triangle" # crush 4 +``` + +This can be directly translated to the Strudel equivalent: + +```js +iter(4, every(3, add.squeeze("10 20"), n("0 1 3").s("triangle").crush(4))) +``` + +Although for a more canonical Strudel expression, we would reorder it +as: + +```js +n("0 1 3").every(3, add.squeeze("10 20")).iter(4).s("triangle").crush(4) +``` + +The Strudel example uses the `.` method call operator for all +operations and combinations, whereas the Tidal example has `#` for the +default method for combining patterns and uses infix operators for +other methods. The relative lack of parenthesis in the Tidal example is partly +due to the way that arguments are applied to Haskell's functions, and +partly due to the use of the `$` operator as an alternative way to +establish precedence and control the order of evaluation. + +Considering the above, we hypothesise that the Haskell syntax is a little +cleaner, but that the Strudel syntax is easier to learn. Our informal +observation is that while Haskell's dollar `$` operator is very useful +in making code easier to work with, it is one of the most difficult +aspects of Tidal use for beginners to learn. On the other hand, the +deeper levels of parenthesis in Strudel code can be difficult to keep +track of, especially while coding under pressure of live musical +performance. However this difficulty can largely be mitigated by +reordering expressions, and further mitigated by supporting editor +features. + +With Strudel, we have little choice but to embrace the affordances and +constraints offered by JavaScript, and while designing a +domain-specific language based on method calls is a +challenge, through creative adoption of functional programming +techniques like partial application, we are so far very happy with the +results. Tidal's functional reactive approach to pattern-making has in +general translated well to JavaScript, and opportunities and +constraints have overall traded off to create a very approachable and +useable live coding environment. + +## The trade-off of flexible typing + +We have identified one problem with porting Tidal to JavaScript where we have missed Haskell's strict typing and type inference. In both Tidal and Strudel, time is rational, where any point in time is represented as the ratio of two integers. This allows representation of musical ratios such that are impossible to represent accurately using the more common floating point numbers. However while libraries are available that support rational numbers in JavaScript, the lack of strict typing means that it is easy to implement pattern methods where computationally expensive conversion from floating point to rational numbers are performed late, and therefore often enough to overload the CPUs, due to the large number of iterative calculations required to estimate a ratio for a given floating point number. To mitigate this problem, we might consider moving to TypeScript in the future. + +# Future Outlook + +The project is still young, with many features on the horizon. As general guiding principles, Strudel aims to be + +1. accessible +2. consistent with Tidal's approach to pattern +3. modular and extensible + +While Haskell's type system makes it a great language for the ongoing development of Tidal's inner representation of pattern, JavaScript's vibrant ecosystem, flexibility and accessibility makes it a great host for more ad-hoc experiments, including interface design. For the future, it is planned to integrate additional alternative sound engines such as Glicol [@lanChaosprintGlicol2022] and Faust [@FaustProgrammingLanguage2022]. Strudel is already approaching feature parity with Tidal, but there are more Tidal functions to be ported, and work to be done to improve compatibility with Tidal's mini-notation. Tidal version 2.0 is under development, which brings a new representation for sequences to its patterns, which will then be brought to Strudel. Besides sound, other ways to render events are being explored, such as graphical, and choreographic output. We are also looking into alternative ways of editing patterns, including multi-user editing for network music, parsing a novel syntax to escape the constraints of JavaScript, and developing hardware/e-textile interfaces. In summary, there is a lot of fun ahead. + +# Links + +The Strudel REPL is available at , including an interactive tutorial. +The repository is at , all the code is open source under the AGPL-3.0 License. + +# Acknowledgments + +Thanks to the Strudel and wider Tidal, live coding, WebAudio and free/open source software communities for inspiration and support. Alex McLean's work on this project is supported by a UKRI Future Leaders Fellowship [grant number MR/V025260/1]. + +# References diff --git a/paper/iclc2023.pdf b/paper/iclc2023.pdf new file mode 100644 index 00000000..d227e89d Binary files /dev/null and b/paper/iclc2023.pdf differ diff --git a/paper/iclc2023x.pdf b/paper/iclc2023x.pdf new file mode 100644 index 00000000..9a31ae22 Binary files /dev/null and b/paper/iclc2023x.pdf differ diff --git a/paper/images/strudel-screenshot2.png b/paper/images/strudel-screenshot2.png new file mode 100644 index 00000000..56673fe5 Binary files /dev/null and b/paper/images/strudel-screenshot2.png differ diff --git a/paper/images/strudelflow.png b/paper/images/strudelflow.png new file mode 100644 index 00000000..72cdf494 Binary files /dev/null and b/paper/images/strudelflow.png differ diff --git a/paper/inconsolata.sty b/paper/inconsolata.sty new file mode 100644 index 00000000..80011e94 --- /dev/null +++ b/paper/inconsolata.sty @@ -0,0 +1,92 @@ +% Copyright 2014 Michael Sharpe +% Based initially on Karl Berry's inconsolata.sty. +% You may freely use, modify and/or distribute this file. + +\def\fileversion{1.05} +\def\filedate{2014/06/22} +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{inconsolata}[\filedate\space v\fileversion] +\message{`inconsolata-zi4' v\fileversion, \filedate\space Text macros for Inconsolata (msharpe)} + +\RequirePackage{textcomp} +\RequirePackage{keyval} + +\newcount\zifour@ocount +\newif\ifzifour@altzero +\newif\ifzifour@noupq +\define@key{zifour}{scaled}[1.0]{\def\zifour@scaled{s*[#1]}} + +\DeclareOption*{% + \begingroup + \edef\x{\endgroup + \noexpand\setkeys{zifour}{\CurrentOption}}% + \x} + +% by default, change \tt to mean zi4. +\newcommand*{\zifour@default}{% + \renewcommand*{\ttdefault}{zi4}% +} + +% option [nott] to avoid changing tt. +\DeclareOption{nott}{% + \renewcommand*{\zifour@default}{}% +} +% option [noupquote] to prevent loading upquote. +\DeclareOption{noupquote}{% + \zifour@noupqtrue}% + +% option var0---use unslashed zero (slashed is default) +\DeclareOption{var0}{% + \zifour@altzerotrue\advance\zifour@ocount \tw@ % +} +\DeclareOption{varl}{% + \advance\zifour@ocount \@ne % +} +\DeclareOption{varqu}{% + \advance\zifour@ocount 4\relax % +} + +\ProcessOptions* +\zifour@default +\edef\zifour@opt{\the\zifour@ocount} +\ifzifour@altzero + \advance\zifour@ocount -\tw@ +\else + \advance\zifour@ocount \tw@ +\fi +\edef\zifour@altopt{\the\zifour@ocount} +% define an \altzero macro which flips to slashed, unslashed +\def\altzero{{\fontfamily{zi4}% + \fontshape{scit}% + \selectfont 0}} + +\def\zifour@T@ne@nc{T1} +\def\zifour@OT@ne@nc{OT1} +\def\zifour@LY@ne@nc{LY1} +\def\zifour@QX@nc{QX} +\def\zifour@TQS{% +\UndeclareTextCommand{\textquotesingle}{\encodingdefault} +\DeclareTextSymbol{\textquotesingle}{TS1}{39}} + +\ifzifour@noupq% do nothing + % Try to correct for wrong slots for QX + \ifx\encodingdefault\zifour@QX@nc + \zifour@TQS + \else + \ifx\encodingdefault\zifour@LY@ne@nc + \zifour@TQS + \fi + \fi +\else + \AtBeginDocument{% + \ifx\encodingdefault\zifour@T@ne@nc % do nothing + \else + \ifx\encodingdefault\zifour@OT@ne@nc % do nothing + \else + \zifour@TQS + \fi + \fi + \usepackage{upquote}} +\fi + +\endinput diff --git a/paper/pandoc/iclc.html b/paper/pandoc/iclc.html new file mode 100755 index 00000000..90406b16 --- /dev/null +++ b/paper/pandoc/iclc.html @@ -0,0 +1,80 @@ +$if(false)$ + +This is a pandoc template and should not be edited. + +$endif$ + + + + + + +$for(author-meta)$ + +$endfor$ +$if(date-meta)$ + +$endif$ + $if(title-prefix)$$title-prefix$ - $endif$$pagetitle$ + +$if(quotes)$ + +$endif$ +$if(highlighting-css)$ + +$endif$ + +$for(css)$ + +$endfor$ +$if(math)$ + $math$ +$endif$ +$for(header-includes)$ + $header-includes$ +$endfor$ + + +$for(include-before)$ +$include-before$ +$endfor$ +$if(title)$ +
+

$title$

+$if(subtitle)$ +

$subtitle$

+$endif$ +
    +$for(author)$ +
  • $author$
  • +$endfor$ +
+$if(date)$ +

$date$

+$endif$ +
+$endif$ +$if(toc)$ +
+$toc$ +
+$endif$ + +

Abstract

+
+$if(abstract)$ +$abstract$ +$else$ +Please provide an abstract in the metadata block at the top of your +markdown document. Refer to template.txt for details. +$endif$ +
+ +$body$ +$for(include-after)$ +$include-after$ +$endfor$ + + diff --git a/paper/pandoc/iclc.latex b/paper/pandoc/iclc.latex new file mode 100755 index 00000000..75fe523a --- /dev/null +++ b/paper/pandoc/iclc.latex @@ -0,0 +1,224 @@ +$if(false)$ + +This is a pandoc template and should not be edited. + +$endif$ +\documentclass[$if(fontsize)$$fontsize$,$endif$$if(lang)$$lang$,$endif$$if(papersize)$$papersize$,$endif$$for(classoption)$$classoption$$sep$,$endfor$]{$documentclass$} + +\usepackage{pandoc/iclc} +$if(linestretch)$ +\usepackage{setspace} +\setstretch{$linestretch$} +$endif$ + +\usepackage{amssymb,amsmath} +\usepackage{ifxetex,ifluatex} +\usepackage{fixltx2e} % provides \textsubscript +\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex + $if(fontfamily)$ + \usepackage{$fontfamily$} + \usepackage{inconsolata} + $else$ + \usepackage{lmodern} + $endif$ + \usepackage[T1]{fontenc} + \usepackage[utf8]{inputenc} +$if(euro)$ + \usepackage{eurosym} +$endif$ +\else % if luatex or xelatex + \ifxetex + \usepackage{mathspec} + \usepackage{xltxtra,xunicode} + \else + \usepackage{fontspec} + \fi + \defaultfontfeatures{Mapping=tex-text,Scale=MatchLowercase} + \newcommand{\euro}{€} +$if(mainfont)$ + \setmainfont{$mainfont$} +$endif$ +$if(sansfont)$ + \setsansfont{$sansfont$} +$endif$ +$if(monofont)$ + \setmonofont[Mapping=tex-ansi]{$monofont$} +$endif$ +$if(mathfont)$ + \setmathfont(Digits,Latin,Greek){$mathfont$} +$endif$ +\fi +% use upquote if available, for straight quotes in verbatim environments +\IfFileExists{upquote.sty}{\usepackage{upquote}}{} +% use microtype if available +\IfFileExists{microtype.sty}{% +\usepackage{microtype} +\UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts +}{} +$if(geometry)$ +\usepackage[$for(geometry)$$geometry$$sep$,$endfor$]{geometry} +$endif$ +$if(lang)$ +\ifxetex + \usepackage{polyglossia} + \setmainlanguage{$mainlang$} +\else + \usepackage[shorthands=off,$lang$]{babel} +\fi +$endif$ +$if(natbib)$ +\usepackage{natbib} +\bibliographystyle{$if(biblio-style)$$biblio-style$$else$plainnat$endif$} +$endif$ +$if(biblatex)$ +\usepackage{biblatex} +$if(biblio-files)$ +\bibliography{$biblio-files$} +$endif$ +$endif$ +$if(listings)$ +\usepackage{listings} +$endif$ +$if(lhs)$ +\lstnewenvironment{code}{\lstset{language=Haskell,basicstyle=\small\ttfamily}}{} +$endif$ +$if(highlighting-macros)$ +$highlighting-macros$ +$endif$ +$if(verbatim-in-note)$ +\usepackage{fancyvrb} +\VerbatimFootnotes +$endif$ +$if(tables)$ +\usepackage{longtable,booktabs} +$endif$ +$if(csl-refs)$ +\newlength{\cslhangindent} +\setlength{\cslhangindent}{1.5em} +\newenvironment{CSLReferences}% + {$if(csl-hanging-indent)$\setlength{\parindent}{0pt}% + \everypar{\setlength{\hangindent}{\cslhangindent}}\ignorespaces$endif$}% + {\par} +$endif$ +$if(graphics)$ +\usepackage{graphicx} +\makeatletter +\def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi} +\def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi} +\makeatother +% Scale images if necessary, so that they will not overflow the page +% margins by default, and it is still possible to overwrite the defaults +% using explicit options in \includegraphics[width, height, ...]{} +\setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio} +$endif$ +\ifxetex + \usepackage[setpagesize=false, % page size defined by xetex + unicode=false, % unicode breaks when used with xetex + xetex]{hyperref} +\else + \usepackage[unicode=true]{hyperref} +\fi +\hypersetup{breaklinks=true, + bookmarks=true, + pdfauthor={$author-meta$}, + pdftitle={$title-meta$}, + colorlinks=true, + citecolor=$if(citecolor)$$citecolor$$else$blue$endif$, + urlcolor=$if(urlcolor)$$urlcolor$$else$blue$endif$, + linkcolor=$if(linkcolor)$$linkcolor$$else$magenta$endif$, + pdfborder={0 0 0}} +\urlstyle{same} % don't use monospace font for urls +$if(links-as-notes)$ +% Make links footnotes instead of hotlinks: +\renewcommand{\href}[2]{#2\footnote{\url{#1}}} +$endif$ +$if(strikeout)$ +\usepackage[normalem]{ulem} +% avoid problems with \sout in headers with hyperref: +\pdfstringdefDisableCommands{\renewcommand{\sout}{}} +$endif$ +\setlength{\parindent}{0pt} +\setlength{\parskip}{6pt plus 2pt minus 1pt} +\setlength{\emergencystretch}{3em} % prevent overfull lines +\providecommand{\tightlist}{%/ + \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} +$if(numbersections)$ +\setcounter{secnumdepth}{5} +$else$ +\setcounter{secnumdepth}{0} +$endif$ +$if(verbatim-in-note)$ +\VerbatimFootnotes % allows verbatim text in footnotes +$endif$ + +$if(title)$ +\title{$title$$if(subtitle)$\\\vspace{0.5em}{\large $subtitle$}$endif$} +$endif$ +$if(author)$ +\author{ +$for(author)$ + $author.name$ \\ + $author.affiliation$\\ + \href{mailto:$author.email$}{$author.email$} + $sep$ \and +$endfor$ +} +$endif$ +\date{$date$} +$for(header-includes)$ +$header-includes$ +$endfor$ + +\begin{document} +$if(title)$ +\maketitle +$endif$ +\begin{abstract} +$if(abstract)$ +$abstract$ +$else$ +Please provide an abstract in the metadata block at the top of the +markdown document. Refer to template.txt for details. $endif$ +\end{abstract} + +$for(include-before)$ +$include-before$ + +$endfor$ +$if(toc)$ +{ +\hypersetup{linkcolor=black} +\setcounter{tocdepth}{$toc-depth$} +\tableofcontents +} +$endif$ +$if(lot)$ +\listoftables +$endif$ +$if(lof)$ +\listoffigures +$endif$ +$body$ + +$if(natbib)$ +$if(biblio-files)$ +$if(biblio-title)$ +$if(book-class)$ +\renewcommand\bibname{$biblio-title$} +$else$ +\renewcommand\refname{$biblio-title$} +$endif$ +$endif$ +\bibliography{$biblio-files$} + +$endif$ +$endif$ +$if(biblatex)$ +\printbibliography$if(biblio-title)$[title=$biblio-title$]$endif$ + +$endif$ +$for(include-after)$ +$include-after$ + +$endfor$ +\end{document} diff --git a/paper/pandoc/iclc.sty b/paper/pandoc/iclc.sty new file mode 100755 index 00000000..b90f8ace --- /dev/null +++ b/paper/pandoc/iclc.sty @@ -0,0 +1,54 @@ + +\def\Hline{\noalign{\hrule height 0.4mm}} +%\newcommand{\bm}[1]{\mbox{\boldmath{$#1$}}} +\newcommand{\figbox}[1]{\fbox{\parbox{\columnwidth}{\centering{ #1 }}}} +\newcommand{\range}[2]{{#1,\cdots,#2\;}} +\newcommand{\secref}[1]{\mbox{Section~\ref{#1}}} +\newcommand{\tabref}[1]{\mbox{Table~\ref{#1}}} +\newcommand{\figref}[1]{\mbox{Figure~\ref{#1}}} +\newcommand{\eqnref}[1]{\mbox{Eq.~(\ref{#1})}} + +\renewcommand{\sfdefault}{phv} +\renewcommand{\rmdefault}{ptm} +\renewcommand{\ttdefault}{pcr} + +\setlength{\paperheight}{297mm} +\setlength{\paperwidth}{210mm} +\setlength{\textheight}{252mm} +\setlength{\textwidth}{172mm} +\setlength{\columnsep}{8mm} +\setlength{\headheight}{0mm} +\setlength{\voffset}{-12mm} +\setlength{\hoffset}{0mm} +\setlength{\marginparwidth}{0mm} +\setlength{\parindent}{2mm} %1pc +\setlength{\topmargin}{-5mm} +\setlength{\oddsidemargin}{-6mm} +\setlength{\evensidemargin}{-6mm} + +\setlength\normallineskip{1\p@} +\setlength\parskip{0\p@ \@plus \p@} +%\def\baselinestretch{0.98} + +\def\normalsize{\@setsize\normalsize{12pt}\xpt\@xpt} +\def\small{\@setsize\small{10pt}\ixpt\@ixpt} +\def\footnotesize{\@setsize\footnotesize{8pt}\viiipt\@viiipt} +\def\scriptsize{\@setsize\scriptsize{8pt}\viipt\@viipt} +\def\tiny{\@setsize\tiny{7pt}\vipt\@vipt} +\def\large{\@setsize\large{14pt}\xiipt\@xiipt} +\def\Large{\@setsize\Large{16pt}\xivpt\@xivpt} +\def\LARGE{\@setsize\LARGE{20pt}\xviipt\@xviipt} +\def\huge{\@setsize\huge{23pt}\xxpt\@xxpt} +\def\Huge{\@setsize\Huge{28pt}\xxvpt\@xxvpt} + +\pagestyle{empty} + +\def\abstract{ + \begin{center}{ + \bf ABSTRACT + } + \end{center} +} +\def\endabstract{\par} + +\flushbottom