Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamically loadable language libraries #248

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 24 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ npm start

## Operators

See `Language` section for alternative language definitions.

To display the list of operators inside of Orca, use `CmdOrCtrl+G`.

- `A` **add**(*a* b): Outputs sum of inputs.
Expand Down Expand Up @@ -70,13 +72,13 @@ To display the list of operators inside of Orca, use `CmdOrCtrl+G`.

## MIDI

The [MIDI](https://en.wikipedia.org/wiki/MIDI) operator `:` takes up to 5 inputs('channel, 'octave, 'note, velocity, length).
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Editor ate trailing spaces, sorry.

The [MIDI](https://en.wikipedia.org/wiki/MIDI) operator `:` takes up to 5 inputs('channel, 'octave, 'note, velocity, length).

For example, `:25C`, is a **C note, on the 5th octave, through the 3rd MIDI channel**, `:04c`, is a **C# note, on the 4th octave, through the 1st MIDI channel**. Velocity is an optional value from `0`(0/127) to `g`(127/127). Note length is the number of frames during which a note remains active. See it in action with [midi.orca](https://git.sr.ht/~rabbits/orca-examples/tree/master/basics/_midi.orca).

## MIDI MONO

The [MONO](https://en.wikipedia.org/wiki/Monophony) operator `%` takes up to 5 inputs('channel, 'octave, 'note, velocity, length).
The [MONO](https://en.wikipedia.org/wiki/Monophony) operator `%` takes up to 5 inputs('channel, 'octave, 'note, velocity, length).

This operator is very similar to the default Midi operator, but **each new note will stop the previously playing note**, would its length overlap with the new one. Making certain that only a single note is ever played at once, this is ideal for monophonic analog synthetisers that might struggle to dealing with chords and note overlaps.

Expand All @@ -94,9 +96,9 @@ It sends two different values **between 0-127**, where the value is calculated a

## MIDI BANK SELECT / PROGRAM CHANGE

This is a command (see below) rather than an operator and it combines the [MIDI program change and bank select functions](https://www.sweetwater.com/sweetcare/articles/6-what-msb-lsb-refer-for-changing-banks-andprograms/).
This is a command (see below) rather than an operator and it combines the [MIDI program change and bank select functions](https://www.sweetwater.com/sweetcare/articles/6-what-msb-lsb-refer-for-changing-banks-andprograms/).

The syntax is `pg:channel;msb;lsb;program`. Channel is 0-15, msb/lsb/program are 0-127, but program will automatically be translated to 1-128 by the MIDI driver. `program` typically correspondes to a "patch" selection on a synth. Note that `msb` may also be identified as "bank" and `lsb` as "sub" in some applications (like Ableton Live).
The syntax is `pg:channel;msb;lsb;program`. Channel is 0-15, msb/lsb/program are 0-127, but program will automatically be translated to 1-128 by the MIDI driver. `program` typically correspondes to a "patch" selection on a synth. Note that `msb` may also be identified as "bank" and `lsb` as "sub" in some applications (like Ableton Live).

`msb` and `lsb` can be left blank if you only want to send a simple program change. For example, `pg:0;;;63` will set the synth to patch number 64 (without changing the bank)

Expand Down Expand Up @@ -149,30 +151,39 @@ All commands have a shorthand equivalent to their first two characters, for exam
- `midi:1;2` Set Midi output device to `#1`, and input device to `#2`.
- `udp:1234;5678` Set UDP output port to `1234`, and input port to `5678`.
- `osc:1234` Set OSC output port to `1234`.
- `lang:clr;default;etc` Incrementally load language libraries (`clr` clears the language library entirely). Multiple languages can be combined.

### Language

Orca language is a "library" of operators (everything that is not a command) that together define its behavior. Orca has the ability to dynamically load and combine multiple operator libraries at runtime, effectively allowing you to reconfigure the language. For example, you may want this for playing older compositions that are no longer compatible with the current Orca, or to experiment with alternative operators without affecting mainline Orca. Language reconfiguration lets you tailor the language to your individual composition.

Orca starts with a `default` library, additional libraries can be loaded and combined via the `lang` command. Library loading is incremental, that is, operators defined in the new library are added to runtime, replacing existing operators. A given library may define all operators or only some. To completely clear the runtime library and start with the blank slate use the `clr` library.

You can see available libraries and their documentation [here](https://github.com/hundredrabbits/Orca/blob/master/desktop/sources/scripts/library).

## Base36 Table

Orca operates on a base of **36 increments**. Operators using numeric values will typically also operate on letters and convert them into values as per the following table. For instance `Do` will bang every *24th frame*.
Orca operates on a base of **36 increments**. Operators using numeric values will typically also operate on letters and convert them into values as per the following table. For instance `Do` will bang every *24th frame*.

| **0** | **1** | **2** | **3** | **4** | **5** | **6** | **7** | **8** | **9** | **A** | **B** |
| :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: |
| **0** | **1** | **2** | **3** | **4** | **5** | **6** | **7** | **8** | **9** | **A** | **B** |
| :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: |
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| **C** | **D** | **E** | **F** | **G** | **H** | **I** | **J** | **K** | **L** | **M** | **N** |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| **O** | **P** | **Q** | **R** | **S** | **T** | **U** | **V** | **W** | **X** | **Y** | **Z** |
| **O** | **P** | **Q** | **R** | **S** | **T** | **U** | **V** | **W** | **X** | **Y** | **Z** |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |

## Transpose Table

The midi operator interprets any letter above the chromatic scale as a transpose value, for instance `3H`, is equivalent to `4A`.

| **0** | **1** | **2** | **3** | **4** | **5** | **6** | **7** | **8** | **9** | **A** | **B** |
| :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: |
| **0** | **1** | **2** | **3** | **4** | **5** | **6** | **7** | **8** | **9** | **A** | **B** |
| :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: |
| _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | A0 | B0 |
| **C** | **D** | **E** | **F** | **G** | **H** | **I** | **J** | **K** | **L** | **M** | **N** |
| C0 | D0 | E0 | F0 | G0 | A0 | B0 | C1 | D1 | E1 | F1 | G1 |
| **O** | **P** | **Q** | **R** | **S** | **T** | **U** | **V** | **W** | **X** | **Y** | **Z** |
| A1 | B1 | C2 | D2 | E2 | F2 | G2 | A2 | B2 | C3 | D3 | E3 |
| C0 | D0 | E0 | F0 | G0 | A0 | B0 | C1 | D1 | E1 | F1 | G1 |
| **O** | **P** | **Q** | **R** | **S** | **T** | **U** | **V** | **W** | **X** | **Y** | **Z** |
| A1 | B1 | C2 | D2 | E2 | F2 | G2 | A2 | B2 | C3 | D3 | E3 |

## Companion Applications

Expand Down
8 changes: 6 additions & 2 deletions desktop/sources/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
<script type="text/javascript" src="scripts/lib/theme.js"></script>
<script type="text/javascript" src="scripts/lib/history.js"></script>
<script type="text/javascript" src="scripts/lib/source.js"></script>
<script type="text/javascript" src="scripts/core/library.js"></script>
<script type="text/javascript" src="scripts/core/library/library.js"></script>
<script type="text/javascript" src="scripts/core/library/base.js"></script>
<script type="text/javascript" src="scripts/core/library/default.js"></script>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the uglier parts. Is there a better way to organize and load these so that new definitions can be added without modifying index.html? Can one JS file import another?

Ideally this would be user loadable.

<script type="text/javascript" src="scripts/core/library/orca157.js"></script>
<script type="text/javascript" src="scripts/core/library/sborca.js"></script>
<script type="text/javascript" src="scripts/core/io.js"></script>
<script type="text/javascript" src="scripts/core/operator.js"></script>
<script type="text/javascript" src="scripts/core/orca.js"></script>
Expand All @@ -30,7 +34,7 @@

client.install(document.body)

window.addEventListener('load', () => {
window.addEventListener('load', () => {
client.start()
client.acels.inject('Orca')
})
Expand Down
13 changes: 10 additions & 3 deletions desktop/sources/scripts/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
/* global Theme */

function Client () {
this.version = 176
this.library = library
this.version = 177
this.libraryName = 'default'
this.library = library[this.libraryName]

this.theme = new Theme(this)
this.acels = new Acels(this)
Expand Down Expand Up @@ -331,7 +332,13 @@ function Client () {
} else {
this.write(this.orca.f < 25 ? `ver${this.version}` : `${Object.keys(this.source.cache).length} mods`, this.grid.w * 0, this.orca.h + 1, this.grid.w)
this.write(`${this.orca.w}x${this.orca.h}`, this.grid.w * 1, this.orca.h + 1, this.grid.w)
this.write(`${this.grid.w}/${this.grid.h}${this.tile.w !== 10 ? ' ' + (this.tile.w / 10).toFixed(1) : ''}`, this.grid.w * 2, this.orca.h + 1, this.grid.w)
this.write(
this.orca.f < 25
? `${this.libraryName}`
: `${this.grid.w}/${this.grid.h}${this.tile.w !== 10 ? ' ' + (this.tile.w / 10).toFixed(1) : ''}`,
this.grid.w * 2,
this.orca.h + 1,
this.grid.w)
this.write(`${this.clock}`, this.grid.w * 3, this.orca.h + 1, this.grid.w, this.clock.isPuppet ? 3 : this.io.midi.isClock ? 11 : this.clock.isPaused ? 20 : 2)
this.write(`${display(Object.keys(this.orca.variables).join(''), this.orca.f, this.grid.w - 1)}`, this.grid.w * 4, this.orca.h + 1, this.grid.w - 1)
this.write(this.orca.f < 250 ? `> ${this.io.midi.toOutputString()}` : '', this.grid.w * 5, this.orca.h + 1, this.grid.w * 4)
Expand Down
25 changes: 24 additions & 1 deletion desktop/sources/scripts/commander.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict'

/* global library */

function Commander (client) {
this.isActive = false
this.query = ''
Expand Down Expand Up @@ -70,7 +72,28 @@ function Commander (client) {
},
write: (p) => {
client.orca.writeBlock(p._x || client.cursor.x, p._y || client.cursor.y, p._str)
}
},
// Language
lang: (p) => {
p.parts.forEach(l => {
if (l === 'clr') {
console.log('Clearing lang'),
client.library = {}
client.libraryName = '---'
} else {
if (l in library) {
console.log(`Incrementally loading lang: ${l}`)
client.library = Object.assign({}, client.library, library[l])
client.libraryName = l
} else {
console.error(`Lang ${l} is not defined`)
return
}
}
client.orca.library = client.library
client.clock.setFrame(0)
})
},
}

// Make shorthands
Expand Down
15 changes: 15 additions & 0 deletions desktop/sources/scripts/core/library/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Orca language is a "library" of operators (everything that is not a command) that together define its behavior. Orca has the ability to dynamically load and combine multiple operator libraries at runtime, effectively allowing you to reconfigure the language. For example, you may want this for playing older compositions that are no longer compatible with the current Orca, or to experiment with alternative operators without affecting mainline Orca. Language reconfiguration lets you tailor the language to your individual composition.

Orca starts with a `default` library, additional libraries can be loaded and combined via the `lang` command. Library loading is incremental, that is, operators defined in the new library are added to runtime, replacing existing operators. A given library may define all operators or only some. To completely clear the runtime library and start with the blank slate use the `clr` library.

## Available libraries

"Complete" means you get a fully functioning Orca by loading just that library, "incremental" means it only defines a subset of operators and you need something loaded before that to get a full Orca (usually `default`).

* `clr`: a special library that unloads all definitions
* `default`: mainline Orca language, loaded at startup (complete)
* `orca157`: Orca [before the BFL breaking change](https://github.com/hundredrabbits/Orca/commit/4fd9ad72aafbb3f0c71139fd36ae421f1d8f352a) (complete)
* `base`: not a very useful library by itself, defines the basics such as comments and numbers
* `sb*`: Sborca collection of alternative operators (see `sborca.md`)

## Usage examples
36 changes: 36 additions & 0 deletions desktop/sources/scripts/core/library/base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
`use strict`

/* global Operator */
/* global library */

library.base = {}

library.base['#'] = function OperatorComment (orca, x, y, passive) {
Operator.call(this, orca, x, y, '#', true)

this.name = 'comment'
this.info = 'Halts line'
this.draw = false

this.operation = function () {
for (let x = this.x + 1; x <= orca.w; x++) {
orca.lock(x, this.y)
if (orca.glyphAt(x, this.y) === this.glyph) { break }
}
orca.lock(this.x, this.y)
}
}

for (let i = 0; i <= 9; i++) {
library.base[`${i}`] = function OperatorNull (orca, x, y, passive) {
Operator.call(this, orca, x, y, '.', false)

this.name = 'null'
this.info = 'empty'

// Overwrite run, to disable draw.
this.run = function (force = false) {

}
}
}