Skip to content

Instantly share code, notes, and snippets.

@iani
Created November 2, 2013 18:54
Show Gist options
  • Select an option

  • Save iani/7282194 to your computer and use it in GitHub Desktop.

Select an option

Save iani/7282194 to your computer and use it in GitHub Desktop.
first alpha concept for org-sc
* ACTIVE orgmode/sc: 1 node/process per buffer. navigate n/p in org mode :orgmode:emacs:livecoding:supercollider:
:PROPERTIES:
:DATE: <2013-09-30 Mon 13:21>
:ENTRYTYPE: note
:END:
** Basic idea: Keep code in orgmode nodes, but edit it in temporary separate buffer
u** Terminology: o-nodes, s-nodes
Since both orgmode and SC use the term node, here we will use o-node and s-node to distinguish between the two.
- o-node :: A node in org-mode (defined by entry starting with one or more *)
- s-node :: An entity in SuperCollider: Group, Synth (possibly also other SC entities to be included here for the purposes of preent project.)
*** Features, advantages
It is preferable to edit the code in a temporary buffer with sclang-mode, so that all the sclang features are available. However, one accumulate the evaluated sclang-code snippets in an o-node in orgmode. This can provide following advantages:
- Each s-node can have its own environment (in SC), which is stored in SC under the first word of the header of the o-node. To prevent duplicate o-node names, one would select or create new s-nodes not in orgmode, but by creating/selecting new buffers (see [[*Simple%20starting%20point%20version:%20Using%20built-in%20buffer%20selection][Simple starting point version: Using built-in buffer selection]]). One might have extra keyboard commands to insert new s-nodes/buffers relative to the node containing the cursor. (In orgmode, one can use a variant of the commands drafted for SC below.)
- The history of each node entity (for example Synth, Group etc.), containing all code evaluated which applies to the node entity, is stored in the body text of the orgmode-node named after that entity, in sc snippets with timestamps, like this:
//: [2013-10-06 Sun 16:29]
~synth = { WhiteNoise.ar(0.1) }.play;
//: [2013-10-06 Sun 16:30]
~synth.free
etc.
(Note: To store seconds and fractions of seconds, I have to create an alternative orgmode timestamp function.)
- Manipulate the order of nodes in an org-mode outline to rearrange the order of SC synth Nodes, thereby keeping a mirror of the structure of the scsynth node tree.
- Use tags to denote the type of object contained in the node
Example for mutually exclusive tag group with keys for fast input:
#+BEGIN_SRC
,#+TAGS: { synth(s) group(g) routine(r) pattern(p) bus(b) buffer(f) }
#+END_SRC
- Use orgmode properties or other drawers for further info, which may be inherited by subnodes in the orgmode ouline tree.
- Store lists of sound files in lists inside orgmode nodes. Use checkboxes to select-unselect files for loading-freeing. It is also possible to use sc to present a path selection dialog for users that prefer that to the emacs path input method. See: [[*Buffers][Buffers]]. Drawers could be used to store lists of files that are needed for one synth in one node (or tree of nodes).
*** Appending evaluated code to the orgmode-node
A possible default method would be:
- When a code region or line is evaluated, then:
- Check the result returned from sc to emacs, by looking at the value of the symbol =sclang-return-string=. If sclang reported an error, then the string will start with the substring "ERROR: ". In that case, the evaluated string is *not* added to the code history of the orgmode node. Otherwise, the evaluated string will be appended at the end of the current node in the orgmode tree, after adding an sclang snippet comment with timestamp like this:
//: [2013-10-06 Sun 17:25]
Seconds and fractions of seconds may also be included in the timestamp (have to find out how to build such a timestamp in emacs-lisp).
Note: TODO: Add a defadvice (advice) to the sclang-mode function(s) that evaluate sclang-code so that the string of the code evaluated is stored in a special buffer "sclang-last-evaluated-string.". The contens of this buffer will be used as string to append the code to the orgmode-node. This is because the user may be evaluating one line or region, or snippet and not the entire sclang-buffer opened from the contents of the orgmode-node. The buffer may contain snippets accumulated from previous evaluations.
*** Test code for obtaining error string from sclang
#+BEGIN_SRC elisp
sclang-return-string
#+END_SRC
#+RESULTS:
: ERROR: Variable 'alphs' not defined.
: in file 'selected text'
: line 1 char 9:
:
: 1 + alphs;
:
: -----------------------------------
: nil
*** To get the contents of a node one must use several functions:
(org-back-to-heading t)
(org-end-of-subtree t)
org-element-at-point
(goto-char (car range))
#+BEGIN_SRC elisp
(org-element-at-point)
#+END_SRC
#+RESULTS:
| src-block | (:language elisp :switches nil :parameters nil :begin 17638 :end 17690 :number-lines nil :preserve-indent nil :retain-labels t :use-labels t :label-fmt nil :hiddenp nil :value (org-element-at-point)\n :post-blank 1 :post-affiliated 17638 :parent nil) |
*** Keyboard commands
- The keyboard prefix for supercollider commands in orgmode is C-s-l (Command-Super-l)
- The above prefix is then to be followed by single kesy such as:
- g = go create/restart/unpause group/synth/pattern/routine
- p = pause group/synth/pattern/routine
- s = stop/free group/synth/pattern/routint
*** sc class draft for node interconnection
Note the whole thing may be tracked in SC in a class called:
Node[Link]Monitor {
var name; // name of sclang emacs buffer containing node
var id; // number of node (synth or group)
var inputs; // dict of inputs by name
var outputs; // dict of outputs by name
}
Draft for IO class:
IO {
var name;
var node;
var bus;
}
Draft:
*** how to represent busses and i/o configuration (org mode) :supercollider:orgmode:
Note: while on an input or output one may add or remove connections to it. For example:
On input in, one may add a new connection from an output. The message bar presents first a choice of synths with outputs to choose a synth, and then a choice of outputs from that synth to choose from.
Same thing with adding an input to an output.
**** nodename
***** inputs
****** in (parameter name of input 1)
property: bus id., if there is an extra bus already connected
nodes writing to in:
******* nodex
******* nodey
******* nodez
****** in2
***** outputs
****** out (parameter name of output 1)
nodes reading from out:
******* nodex
******* nodey
******* nodez
****** out2 (parameter name of output 2)
nodes reading from out2:
******* nodex
******* nodey
******* nodez
*** How to add connections to inputs and outputs
When one is on an org mode node representing an input:
One can type
Analogously: When one is in an org mode node representing an output,
*** Representation 1 (general) SC entities in the orgmode file
- Each orgnode node represents an scsynth Node instance (Group or Synth)
- The order of the orgmode nodes corresponds to the node order in scsynth.
- Moving a node or subtree in orgmode triggers corresponding movement of nodes in scsynth.
- To
*** In sclang mode:
Write snippet in snippet style:
//:
Then type run snippet shortcut (Was it this one?: C-c .)
** First bottom-up steps in sc
:PROPERTIES:
:DATE: <2013-10-04 Fri 17:55>
:END:
*** Sending data from sc to emacs lisp
The first step has been done, using defadvice on sclang-process-filter. However, a next step is necessary: Modify the advice to type =after= (instead of =before= and add a new step, after the sclang-process-filter main function evaluation, and after saving the received data string in a variable, to run any required functions in emacs lisp which require the output from the sclang-process. Possibly use (run-hooks 'name-of-hook'). This is needed to make sure that the asynchrous call of an emacs lisp function that requires data from sclang actually gets the data after the data is completed. Step-by-step description:
1) Emacs needs some info from SC, in order to display a selection menu, fill a buffer etc.
2) Emacs creates an sclang buffer and uses it to input and evaluates this code in SC.
3) Emacs registers a function to run after it has received the data that it needs from SC
4) Emacs waits for SC to computer and return the required data.
5) When the data is received, Emacs does 4 things:
1. It converts the string received from sc for Emacs compatibility
(this is done by the sclang-process-filter function.)
2. It stores the data received in a variable (elisp symbol) for further access
(In fact this storing may be to a local variable, via =let= instead of a global variable.)
3. It runs the function registered in 3) above to process the data received from 4).
4. It unregisters the function from the sclang-process-filter hook.
*** Using one buffer per process
For each of the following kinds of objects: Groups, Synths, playing patterns (Pbinds), and Routines, it is proposed to have can be one emacs buffer per object. This buffer is coupled to its own currentEnvironment in SuperCollider. Also one may switch focus to the object in order to modify it by selecting its buffer through the buffer name. Messages can be addressed to the object will consistently address this object. (This should be explained more clearly below).
Buffers and Busses are two additional categories, for which the practical use of one-object-per-buffer will be also explored, at a later stage of the development.
Each of the above categories has a separate list of buffers. So the list of buffers displayed for selection is a different one depending on the object category selected above, using keyboard shortcuts in emacs.
The buffer lists are stored in the following 6 variables (symbols) in Emacs:
: sc-synths sc-groups sc-routines sc-patterns sc-buffers sc-busses
Additionally these variables can be local to buffers in order to enable having different lists of synths for buffers whose synths belong to different servers. Again, this is a matter to be explored later on. The next section outlines the use of lists to provide org-complete features for selecting SC objects easily in emacs.
**** Note: Avoiding losing references to objects
Care must be taken not to lose access to running processes such as synths, by overwriting a variable that stores a running synth with another, different synth. How to do this here?
*** Using org-complete completion from lists, wherever possible.
Following lists could be used for completion. Some of these lists are global, other are context-sensitive, i.e. each buffer has its own list for auto-completion selection.
These lists can be stored in variables. The global lists in global emacs-lisp variables (symbols), the local lists in buffer-local variables (see: [[http://www.gnu.org/software/emacs/manual/html_node/elisp/Intro-to-Buffer_002dLocal.html#Intro-to-Buffer_002dLocal][buffer local bindings]], choose between two different ways of doing this: make-local-variable vs. make-variable-buffer-local).
Additionally, some lists should be stored in variables shared by buffers that refer for example to the same server. Do we have to use association lists for that? Or just lists and manipulate the same list as function for a buffer local variable this way:
=buffer1= (default server)
Local variable =buffers=, whose value is list =x=.
=buffer2= (also default server)
Also local variable =buffers=, whose value is again list =x=.
If I change list x by nconc (does this also work with cons?), add-to-list, delete, then the list object is modified. So modifying the list =x= will have the same effect for the values contained in the local variables =buffers= in both buffers =buffer1= and =buffer2=.
Following lists would be useful:
**** SynthDefs
SynthDefs loaded on server to which emacs buffer belongs.
**** Buffers
Buffers loaded on server to which emacs buffer belongs.
Question: Can one use something like Projectile to switch between different groups of files to load into buffers?
One might create a variant of projectile-switch-project that pops up a buffer with a list of file paths to choose from (with check-boxes next to each path)
The list would be of this form:
Sound files in file:~/Music/sounds/animals/ :
(Type Control-c-Control-c on a checkbox to toggle selection)
- [-] select all (need to add something to orgmode for this to work) [4/5]
- [X] [[~/Music/sounds/animals/antelope.aiff][antelope]]
- [ ] [[~/Music/sounds/animals/bear.aiff][bear]]
- [X] [[~/Music/sounds/animals/cat.aiff][cat]]
- [X] [[~/Music/sounds/animals/cheetah.aiff][cheetah]]
- [X] [[~/Music/sounds/animals/dog.aiff][dog]]
... etc.
Also, one would store previously selected folders and perhaps even configurations in a project list on file, in similar manner to projectile.
**** Parameters
Parameters of process to which emacs buffer belongs.
**** Synths
**** Groups
**** Pbind-type Pattern-Playeer processes
**** Routines
**** Target(s) (not a list, but info to be stored/displayed!, or yet a list of possible targets)?
Targets can be all the current Groups and Synths. The current process can be placed relative to any one of them.
**** Busses?
**** Possible inputs?
**** Possible outputs?
*** Simple starting point version: Using built-in buffer selection
Use a single character prefix to group buffers according to object:
s: synth
g: group
r: routine
p: pattern
b: bus
f: buffer
For example a buffer named s:grains holds a synth. A buffer named g:fx holds a group. The current buffer selection mechanism of Emacs immediately selects the buffers belonging to a group if one enters the initial characters "s:", "g:" etc.
To automatically use a private currentEnvironment Dictionary for each buffer:
Do =defadvice= (=after=) to the buffer selection/creation function =ido-switch-buffer= to add a function which evaluates sclang code to (create and) install a dictionary as currentEnvironment for the buffer.
The lisp code for the advice would be something like this:
#+BEGIN_SRC emacs-lisp
(let ((bufname (buffer-name (current-buffer))))
(when (string-match "^[sgrpbf]:" buffer-name)
(sclang-mode) ; turn on sclang mode for this buffer
() ; possibly insert snippet header with timestamp
;; and position cursor after snippet header
(with-buffer ) ; install eniv in emacs
))
#+END_SRC
The sc code evaluated inside the =with-buffer= call should be something like:
: BufferEnvironments.switchEnvironment('s:foo')
(Note the single quote used to pass the buffer name as symbol above. Escape eventual ' single quotes in the buffer name?)
Where the switchEnvironment method is defined as:
#+BEGIN_EXAMPLE
BufferEnvironments {
classvar <bufferEnvironments;
*switchEnvironment { | bufferName |
// Note: bufferName must be a symbol
var envir;
envir = bufferEnvironments[bufferName];
if (envir.isNil) {
envir = Environment.new;
bufferEnvironments[bufferName] = envir;
};
Environment.pop; // remove previous environment from stack
Environment.push(envir); // push new environment on stack and make it current
}
}
#+END_EXAMPLE
*** Keyboard commands and code entry mechanism in SuperCollider
Here are proposed Four groups of commands. Each group of commands deals differently with the selection of the buffer and of the possible actions on it. The types of actions corresponding to these groups of commands are:
0. (Short default for quick input and tests:)
Select or create a buffer in the synth buffer list and insert a template for creating a synth.
This is bound to the keyboard command control-super-return.
1. Select or create a buffer from one of the 6 categories of objects. Do nothing else.
2. On an already selected buffer, perform an action that changes the object of the buffer.
(Such actions can be: Set a parameter of a synth, move a node, pause or run a synth etc.)
3. Starting on the already selected buffer, select or create a new object whose object that is
linked to the current buffer, in one of several ways:
- The objecct of the initial buffer serves as relative Node target for the object of the second buffer
- The object of the initial buffer is a bus to which the object of the second buffer is writing / reading.
4. Select or create a buffer and then insert a template for modifying its object.
(This is a combination of 1 and 2 above, in one sequence of keyboard commands and buffer selections).
Following is a first proposal of key bindings for the three categories of actions described above:
**** Category 1: Select or create a new buffer and then display it only.
The key combinations for selecting the type of object that this buffer will belong to could be:
- Control-alt-s = synth
- Control-alt-g = group
- Control-alt-r = routine
- Control-alt-p = pattern
- Control-alt-f = buffer (?)
- Control-alt-b = bus (?)
**** Category 2: Modify the status of the currently selected buffer
Insert a template for modifying parameter, running status, or position of object of currently selected buffer. Key combinations and actions are:
- super-shift-n = create new instance (replacing old one if present)
- super-shift-p = pause
- super-shift-r = run (or create new if previous one is freed)
- super-shift-f = free
- super-shift-h = move to head
- super-shift-t = move to tail
- super-shift-b = move before
- super-shift-a = move after
- super-shift-s = set parameter
- super-shift-m = map parameter
- super-shift-q = query status:
- Is playing - or is freed? is running - or is paused?
- Target
**** Category 3: Add/move/link a new/selected object in relation to the object of the current buffer
- Control-super-shift-s = Add/move Synth in relation to the present target
Specification of the place to add/move the synth is given by pressing one more key, as follows:
- h (or also =return=?) = move/add to head
- t = move/add to tail
- b = move/add before
- a = movce/add after
- r = replace
- Control-super-shift-g = Add/move Group in relation to the present target (as with Synth above)
- Control-super-shift-r = Add/move routine in relation to the present target
i.e. affecting parameters or nodes of the present target through =set= or similar messages.
See EventModel class in Lilt2 as a way for implementing this through environment variables.
- Control-super-shift-p = Same as above, for patterns
- Control-super-shift-f = buffer (?)
- Control-super-shift-b = When the selected buffer is a bus, connect the synth of the new object
to an input or output of the bus. The manner of linking is selected by typing one more key as follows:
- r = the new object "reads" one of its inputs from the selected bus
For example: =~synth.set(\in, ~bus.index);=
- w = the new object "writes" an output to the selected bus.
For example: =~synth.set(\out, ~bus.index)=
**** Category 4: Select or create a new object/buffer and insert template for changing it
- Control-alt-c followed by one further key combination selected from following two groups:
***** 4a: Create new instance of object with default configuration
(Group 1: Single keys without modifier)
This inserts a template for creating a new instance of the type of object to which the buffer belongs, in its default place (head of root group in default server).
- s = synth
- g = group
- r = routine
- p = pattern
- f = buffer
- b = bus
***** 4b: Modify the status of the existing object corresponding to the buffer
This inserts a template for modifying the node parameters, running status, or position of the selected object:
- control-p = pause
- control-r = run
- control-f = free
- control-h = move to head
- control-t = move to tail
- control-b = move before
- control-a = move after
- control-s = set parameter
- control-m = map parameter
**** Following are older tries: :ARCHIVE:
***** Select and perform an action on the current buffer
This action could be triggered with one of two keyboard sequences:
****** Short keyboard command: create new instance with default configuration
Control-alt-return: Insert template for creating object in default position in synth tree (head of root node)
****** Short keyboard command: create new instance with default configuration
****** Select actions to perform on current object in buffer
Note: Control-alt-c is free so it will be used as prefix, both in org mode and in sclang mode (globally) Let's try it.
Here is a step-by-step summary draft of the entry mechanism:
- Step 0-1 Select a category of objects for action (groups, synths, patterns etc.)
- Step 2: Select an object from the above category from a list offered from
org-complete, or create a new one by entering a new name
- On this object select an action by typing a key (key-action pairs are displayed)
- Depending on the action selected, one may be offered to select a second object as
parameter of the action (e.g. a Node to act as target)
Here is a step-by-step detailed draft of the entry mechanism:
***** Step 0+1 Together:
Three alternative starting points (all of these to be implemented):
****** Select group of objects to work with by typing one of the following 3-key combinations:
- Control-alt-g = group
- Control-alt-s = synth
- Control-alt-p = pattern
- Control-alt-r = routine
- Control-alt-b = buffer (?)
- Control-alt-B = bus (?)
****** Fast synth selection/creation
- Control-alt-return: Select or create synth. If creating, add at head of root group.
Alternatively one might do the steps 0 and one in a sequence of keys:
***** (older alternative:) Step 0: Invoke special-sc-buffer-entry-method by Control-alt-c (c-s-c)
***** (older alternative:) Step 1: Select object group to which action applies by a single letter:
- g = group
- s = synth
- p = pattern
- r = routine
- b = buffer (?)
- B = bus (?)
***** Step 2: Select buffer/object (or enter new name to create a new one)
Selection is done from a list displayed on the message buffer and optionally on a temporary full-size buffer window, in the manner of org-completing-read.
***** Step 3: Select action on the object/buffer
- return = Edit buffer
- control-
= add/move to head of root group
- control-return = add/move to tail of root group
- h = add/move to head of group selected from menu (org-complete)
- t = add/move to tail of group selected from menu (org-complete)
- b = add/move before selected object (org-complete)
- a = add/move after currently selected object (org-complete)
- g = "go" ->
- if process is paused, then run.
- else if it is stopped, create new (node), or rest and run (routine)
Note: "go" is equivalent to "new" if a new buffer is created with a new name.
- p = "pause"
- f = "free"
- r = "reset" (for routines, patterns) (?)
- i = add input
- o = add output
- I = remove input
- O = remove output
- c = clone (make a copy on a new buffer, with new name)
- s = set a parameter value
- m = map a parameter to a bus
***** Step 4: Select and/or edit code from template created by choices above in buffer
***** Step 5: Run final snippet of code created by steps above, using C-c . shortcut.
*** alternative keys
Also, in sclang the following are bound to prelude commands that are not particularly useful:
C-c n (new node?)
C-c g (new group?)
#+BEGIN_SRC elisp
(defun sclang-command-test () (interactive) (message "hello"))
(global-set-key (kbd "C-s-c 1") 'sclang-command-test)
#+END_SRC
#+RESULTS:
: sclang-command-test
*** possible commands for node/process buffers in sclang
**** sclang-new-synth
Create a new synth. Info needed (to get from user:)
- Name of target (group/synth) where this synth is created
- Name of buffer (= "name of synth")
- Add method: before, after, head, tail (and others)
Draft:
#+BEGIN_SRC elisp
(org-completing-read "test" '("a" "bel"))
#+END_SRC
#+RESULTS:
: bel
**** sclang-buffer
Select a buffer corresponding to an sc object (group, synth, routine, pattern)
**** sclang-new-group
**** sclang-new-routine
**** sclang-new-pattern
** In org mode
** For node trees consider also tree-mode.el
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment