-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 9ac93e3
Showing
5 changed files
with
241 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
bin/ | ||
docs/ | ||
*.edscript | ||
*.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# NimEdScript | ||
|
||
A wrapper around EdisonScript/JavaScript in FL Studio for Nim. | ||
|
||
It's important to note that EdisonScript's JS implementation is virtually lower than JavaScript 1.0, it lacks an enormous amount of features from the JavaScript language as it's mostly supposed to be a scripting language based around Pascal. The Nim JS output needs some transforming to get right, and until I've done something for that it's best to clone this repo, run nimble buildTests then copy the output in the bin folder to the FL studio scripts folder. | ||
|
||
List of things that do not work in edscript: | ||
|
||
* Math.trunc therefore vanilla Nim `mod` (hangs) | ||
* The expression `typeof Int8Array` if Int8Array isn't defined, breaks nim default header even if unused | ||
* Object default constructors like `var result = {a: 0, b: 0, c: 0, d: 0}; result = otherVar;` (slows it down a LOT, noinit doesn't change anything, use emit/importc/nodecl) | ||
* Break/continue with labels, however labels and do/while loops are allowed | ||
* Switch statement | ||
* Any subscript access of any kind | ||
* The identifier "base" whether variable or object key | ||
|
||
This needs the following post-codegen transforms: | ||
|
||
* Add `script "Script name" language "javascript"` to top of file | ||
* 'base:' -> '"base":', if theres a var named base tough luck | ||
* remove/comment out/simplify default nim primitive array header | ||
* simply remove labels from labeled break/continue statements, this might break some block contraptions | ||
|
||
This makes the following impossible: | ||
|
||
* Anything that generates `nimCopy` | ||
- Assigning object types to variables | ||
* Anything that generates nim string/seq behaviour | ||
- Use cstring and JS arrays | ||
|
||
Also to note, |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# Package | ||
|
||
version = "0.1.0" | ||
author = "hlaaftana" | ||
description = "FL Studio EdisonScript JS wrapper " | ||
license = "MIT" | ||
srcDir = "src" | ||
skipDirs = @["tests"] | ||
|
||
# Dependencies | ||
|
||
requires "nim >= 0.18.0" | ||
|
||
task docs, "Build documentation": | ||
exec "nim doc -o:docs/nimedscript.html src/nimedscript.nim" | ||
|
||
import ospaths, strformat, strutils | ||
|
||
proc transform(text: string): string = | ||
# i would have done all of this better with regex or pegs or whatever but this'll have to do unless we hit a barrier | ||
result = text | ||
result = result.replace("if (typeof ", "//") # edscript doesn't like typeof undefined checking | ||
result = result.replace("base:", "\"base\":") # it also doesn't like "base" as an identifier | ||
result = result.replace("break ", "break; ") # it also doesn't like labeled break/continue | ||
|
||
task buildTests, "Compiles tests to javascript": | ||
for test in listFiles("tests"): | ||
let split = splitFile(test) | ||
if split.ext == ".nim": | ||
let path = "bin/" & split.name & ".edscript" | ||
exec "nim js --gc:none -d:release -o:" & path & " " & test | ||
writeFile(path, &"""script "{split.name}" language "javascript"; | ||
{transform(readFile(path))}""") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
when not defined(JS): | ||
{.error: "nimedscript only works in JS".} | ||
|
||
import jsffi | ||
|
||
type | ||
single* = float32 | ||
Sample* = single | ||
|
||
NormalizeFormatMode* = int | ||
|
||
PasteMode* {.pure.} = enum | ||
## What to do with the old audio when pasting | ||
insert ## Move the old audio to after the pasted audio | ||
replace ## Forget about the old audio and use the pasted audio instead | ||
mix ## Play them both at the same time | ||
|
||
Region* {.pure, importc: "TRegion".} = object | ||
sampleStart* {.importc: "SampleStart".}, sampleEnd* {.importc: "SampleEnd".}: int | ||
name* {.importc: "Name".}, info* {.importc: "Info".}: cstring | ||
time* {.importc: "Time".}: single | ||
keyNum* {.importc: "KeyNum".}: int | ||
|
||
Sound* {.pure, importc: "TSample".} = object | ||
length* {.importc: "Length".}, numChans* {.importc: "NumChans".}, | ||
sampleRate* {.importc: "Samplerate".}, regionCount* {.importc: "RegionCount".}: int | ||
|
||
Editor* {.pure, importc: "TEditor".} = object | ||
sound* {.importc: "Sample".}: Sound | ||
|
||
Input* {.pure, importc: "TInput".} = object | ||
defaultValue* {.importc: "DefaultValue".}, value* {.importc: "Value".}: float | ||
valueAsInt* {.importc: "ValueAsInt".}: int | ||
min* {.importc: "Min".}, max* {.importc: "Max".}: float | ||
|
||
ScriptDialog* {.pure, importc: "TScriptDialog".} = object | ||
|
||
var | ||
CRLF* {.importc, nodecl.}: cstring | ||
## \r\n convenience | ||
editor* {.importc: "Editor", nodecl.}: Editor | ||
## Current Edison editor object | ||
editorSample* {.importc: "EditorSample", nodecl.}: Sound | ||
## Sample currently in editor | ||
scriptPath* {.importc: "ScriptPath", nodecl.}: cstring | ||
## Path of current script file | ||
|
||
proc createScriptDialog*(title, description: cstring): ScriptDialog {.importc: "CreateScriptDialog".} | ||
## Pops up a dialog in the editor with a title `title` and description `description` | ||
## and returns it as an object | ||
proc progressMsg*(msg: cstring; pos, total: int) {.importc: "ProgressMsg".} | ||
## Shows a progress message `msg` with the progress bar being ``pos / total`` full | ||
proc showMessage*(s: cstring) {.importc: "ShowMessage".} | ||
## Shows a message `s` on screen | ||
|
||
var | ||
nfNumChannels* {.importc.}: NormalizeFormatMode | ||
nfFormat* {.importc.}: NormalizeFormatMode | ||
nfSamplerate* {.importc.}: NormalizeFormatMode | ||
nfAll* {.importc.}: NormalizeFormatMode | ||
|
||
using | ||
self: Region | ||
|
||
proc newRegion*(): Region {.importcpp: "TRegion(@)", constructor.} | ||
proc copy*(self, source: Region) {.importcpp: "#.Copy(@)".} | ||
|
||
using | ||
self: Sound | ||
position, channel: int | ||
x1, x2: int | ||
vol: single | ||
|
||
proc newSound*(): Sound {.importcpp: "TSample(@)", constructor.} | ||
## Creates a new sample | ||
proc getSampleAt*(self, position, channel): Sample {.importcpp: "#.GetSampleAt(@)".} | ||
## Gets individual sample (frame) of sound sample `self` at `position` in `channel` | ||
proc setSampleAt*(self, position, channel; value: Sample) {.importcpp: "#.SetSampleAt(@)".} | ||
## Sets individual sample (frame) of sound sample `self` at `position` in `channel` to `value` | ||
|
||
template eachChannel*(self; body) {.dirty.} = | ||
{.emit: ["var sound = ", self].} | ||
var sound {.importc, nodecl.}: Sound | ||
for chan {.noinit.} in 0 ..< sound.numChans: | ||
var samples {.nodecl.}: distinct int | ||
|
||
template `[]`(sample: type(samples), position): Sample = | ||
getSampleAt(sound, position, chan) | ||
|
||
template `[]=`(sample: type(samples), position; value: Sample) = | ||
setSampleAt(sound, position, chan, value) | ||
|
||
body | ||
|
||
using self: Sound | ||
|
||
proc center*(self, x1, x2) {.importcpp: "#.CenterFromTo(@)".} | ||
## Centers audio between sample positions x1 and x2 | ||
proc normalize*(self, x1, x2, vol, onlyIfAbove = false): float {.importcpp: "#.NormalizeFromTo(@)".} | ||
## Normalizes audio between sample positions x1 and x2 | ||
proc amp*(self, x1, x2, vol) {.importcpp: "#.AmpFromTo(@)".} | ||
## Amplifies audio between sample positions x1 and x2 by volume vol | ||
proc reverse*(self, x1, x2) {.importcpp: "#.ReverseFromTo(@)".} | ||
## Reverses audio between sample positions x1 and x2 | ||
proc reversePolarity*(self, x1, x2) {.importcpp: "#.ReversePolarityFromTo(@)".} | ||
## Reverses polarity of audio between x1 and x2 | ||
proc swapChannels*(self, x1, x2) {.importcpp: "#.SwapChannelsFromTo(@)".} | ||
proc insertSilence*(self, x1, x2) {.importcpp: "#.InsertSilence(@)".} | ||
proc silence*(self, x1, x2) {.importcpp: "#.SilenceFromTo(@)".} | ||
proc noise*(self, x1, x2; mode = 1; vol = 1.0) {.importcpp: "#.NoiseFromTo(@)".} | ||
proc sine*(self, x1, x2; freq, phase: float, vol = 1.0) {.importcpp: "#.SineFromTo(@)".} | ||
proc paste*(self; aSound: Sound; x1, x2: int; mode = PasteMode.insert) {.importcpp: "#.PasteFromTo(@)".} | ||
proc loadFromClipboard*(self) {.importcpp: "#.LoadFromClipboard(@)".} | ||
proc delete*(self, x1, x2; copy = false) {.importcpp: "#.DeleteFromTo(@)".} | ||
proc trim*(self, x1, x2) {.importcpp: "#.TrimFromTo(@)".} | ||
|
||
proc msToSamples*(self; time: float): Sample {.importcpp: "#.MsToSamples(@)".} | ||
proc samplesToMs*(self; time: Sample): float {.importcpp: "#.SamplesToMs(@)".} | ||
|
||
proc loadFromFile*(self; filename: cstring) {.importcpp: "#.LoadFromFile(@)".} | ||
proc loadFromFile_Ask*(self) {.importcpp: "#.LoadFromFile_Ask(@)".} | ||
proc normalizeFormat*(self; source: Sound; mode: NormalizeFormatMode = nfAll) {.importcpp: "#.NormalizeFormat(@)".} | ||
proc getRegion*(self; index: int): Region {.importcpp: "#.GetRegion(@)".} | ||
proc addRegion*(self; name: cstring, sampleStart: int, sampleEnd = high(int)): int {.importcpp: "#.AddRegion(@)".} | ||
proc deleteRegion*(self; index: int) {.importcpp: "#.DeleteRegion(@)".} | ||
|
||
using self: Editor | ||
|
||
proc getSelectionInSamples*(self; x1, x2: int): bool {.importcpp: "#.GetSelectionS(@)".} | ||
proc getSelectionInMilliseconds*(self; x1, x2: float): bool {.importcpp: "#.GetSelectionMS(@)".} | ||
|
||
using self: ScriptDialog | ||
|
||
proc newScriptDialog*(): ScriptDialog {.importcpp: "TScriptDialog(@)", constructor} | ||
proc addInput*(self; name: cstring, value: float): Input {.importcpp: "#.AddInput(@)".} | ||
proc addInputKnob*(self; name: cstring, value, min, max: float): Input {.importcpp: "#.AddInputKnob(@)".} | ||
proc addInputCombo*(self; name, valueList: cstring, value: int): Input {.importcpp: "#.AddInputCombo(@)".} | ||
proc getInput*(self; name: cstring): Input {.importcpp: "#.GetInput(@)".} | ||
proc getInputValue*(self; name: cstring): float {.importcpp: "#.GetInputValue(@)".} | ||
proc getInputValueAsInt*(self; name: cstring): int {.importcpp: "#.GetInputValueAsInt(@)".} | ||
proc execute*(self): bool {.importcpp: "#.Execute(@)".} | ||
|
||
proc `[]`*(self; name: cstring): Input {.inline.} = getInput(self, name) | ||
proc `[]`*(self; name: cstring, T: typedesc[float]): T {.inline.} = getInputValue(self, name) | ||
proc `[]`*(self; name: cstring; T: typedesc[int]): T {.inline.} = getInputValueAsInt(self, name) | ||
|
||
proc free*(self: Region | Sound | Editor | Input | ScriptDialog) {.importcpp: "#.Free()".} | ||
|
||
template `+`*(init: string | cstring, second: untyped): untyped = | ||
bind toJs, to | ||
to(toJs(cstring(init)) + toJs(second), cstring) | ||
|
||
proc `%`*[T](a, b: T): T {.importcpp: "# % #".} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import ../src/nimedscript | ||
|
||
proc amp(value: float) = | ||
var x1, x2 = 0 | ||
discard editor.getSelectionInSamples(x1, x2) | ||
|
||
for n in x1..x2: | ||
let x = n - x1 | ||
if x % 10000 == 0: | ||
progressMsg("Processing", x, x2 - x1) | ||
eachChannel(editorSample): | ||
samples[n] = Sample(samples[n].float * value) | ||
|
||
let form = createScriptDialog("Amp", "Simple amplification.") | ||
try: | ||
let volume = form.addInputKnob("Volume", 1, 0, 2) | ||
if form.execute(): | ||
amp(volume.value) | ||
finally: | ||
form.free() |