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

Strudelize Hazel #1395

Draft
wants to merge 3 commits into
base: dev
Choose a base branch
from
Draft

Strudelize Hazel #1395

wants to merge 3 commits into from

Conversation

disconcision
Copy link
Member

This wraps some Strudel functions in order to create audio in Hazel. Right now, it wraps only the Note function, which takes a string corresponding to a Strudel/TidalCycles cycle. This is an eDSL string which can take a space-separated list of notes (a-g), and a variety of other notations. Everything in the string is played over a second, so the more notes the faster it is.

Should be functional now on the build server; see docs/sounds:

Screenshot 2024-09-16 at 10 55 29 PM

The plan is to wrap a few more functions, defunctionalizing to create a simple ADT corresponding to Strudel code. This could be used as a basis for a future more elaborate eDSL using projectors/livelits.

Right now, audio plays whenever the program evaluates to a Note. It can be stopped via a button, and the playing audio restarts every time a valid Strudel program is returned, which is different than the last valid Strudel program for which playing began

let exampleUse: unit => unit =
() => {
initStrudel();
playNote("<c a f e>(3,8)");
Copy link
Contributor

Choose a reason for hiding this comment

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

@disconcision
Copy link
Member Author

@7h3kk1d did you look into the below style of binding js fns when doing the ninjakeys thing? claude suggested it but i couldn't get it to work properly. interestingly, different errors on dev and release. in dev, it was trying to access the fns on an object called runtime, eg erroring on calling runtime.play. in release, it had a more specific error about the method not being implemented

/* First, we need to declare the external JavaScript functions */
[@bs.val] external initStrudel: unit => unit = "initStrudel";
[@bs.val] external hush: unit => unit = "hush";

/* For the `note` function, we'll need to create a wrapper */
[@bs.val] external noteRaw: string => 'a = "note";

/* For `jux` and `rev`, we'll create bindings */
[@bs.send] external jux: ('a, 'a => 'a) => 'a = "jux";
[@bs.val] external rev: 'a => 'a = "rev";

/* For `play`, we'll create a binding */
[@bs.send] external play: 'a => unit = "play";

@7h3kk1d
Copy link
Contributor

7h3kk1d commented Sep 18, 2024

@7h3kk1d did you look into the below style of binding js fns when doing the ninjakeys thing? claude suggested it but i couldn't get it to work properly. interestingly, different errors on dev and release. in dev, it was trying to access the fns on an object called runtime, eg erroring on calling runtime.play. in release, it had a more specific error about the method not being implemented

/* First, we need to declare the external JavaScript functions */
[@bs.val] external initStrudel: unit => unit = "initStrudel";
[@bs.val] external hush: unit => unit = "hush";

/* For the `note` function, we'll need to create a wrapper */
[@bs.val] external noteRaw: string => 'a = "note";

/* For `jux` and `rev`, we'll create bindings */
[@bs.send] external jux: ('a, 'a => 'a) => 'a = "jux";
[@bs.val] external rev: 'a => 'a = "rev";

/* For `play`, we'll create a binding */
[@bs.send] external play: 'a => unit = "play";

I don't think I tried the bucklescript annotation. There was another syntax that I couldn't use because my function was called open which is a keyword in ocaml. Let me find it.

Comment on lines +4 to +67
let initStrudel: unit => unit =
() => {
let initStrudelFn = Js.Unsafe.js_expr("window.initStrudel");
Js.Unsafe.fun_call(initStrudelFn, [||]);
};

let hush: unit => unit =
() => {
let hushFn = Js.Unsafe.js_expr("window.hush");
Js.Unsafe.fun_call(hushFn, [||]);
};

let note: string => Js.Unsafe.any =
pattern => {
let noteFn = Js.Unsafe.js_expr("window.note");
Js.Unsafe.fun_call(noteFn, [|Js.Unsafe.inject(Js.string(pattern))|]);
};

let rev: Js.Unsafe.any => Js.Unsafe.any =
pattern => {
let revFn = Js.Unsafe.js_expr("window.rev");
Js.Unsafe.fun_call(revFn, [|Js.Unsafe.inject(pattern)|]);
};

let jux: (Js.Unsafe.any, Js.Unsafe.any => Js.Unsafe.any) => Js.Unsafe.any =
(pattern, f) => {
Js.Unsafe.meth_call(pattern, "jux", [|Js.Unsafe.inject(f)|]);
};

let play: Js.Unsafe.any => unit =
pattern => {
Js.Unsafe.meth_call(pattern, "play", [||]);
};

/* Wrapper function to chain methods */
let playNote: string => unit =
pattern => {
let n = note(pattern);
let j = jux(n, rev);
play(j);
};

/* Example usage function */
let exampleUse: unit => unit =
() => {
initStrudel();
playNote("<c a f e>(3,8)");
};

/* Function to stop the music */
let stopMusic: unit => unit = () => hush();

/* Function to initialize Strudel when the DOM is loaded */
let initOnLoad: unit => unit =
() => {
let addEventListenerFn = Js.Unsafe.js_expr("window.addEventListener");
Js.Unsafe.fun_call(
addEventListenerFn,
[|
Js.Unsafe.inject(Js.string("DOMContentLoaded")),
Js.Unsafe.inject(Js.wrap_callback(_ => initStrudel())),
|],
);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
let initStrudel: unit => unit =
() => {
let initStrudelFn = Js.Unsafe.js_expr("window.initStrudel");
Js.Unsafe.fun_call(initStrudelFn, [||]);
};
let hush: unit => unit =
() => {
let hushFn = Js.Unsafe.js_expr("window.hush");
Js.Unsafe.fun_call(hushFn, [||]);
};
let note: string => Js.Unsafe.any =
pattern => {
let noteFn = Js.Unsafe.js_expr("window.note");
Js.Unsafe.fun_call(noteFn, [|Js.Unsafe.inject(Js.string(pattern))|]);
};
let rev: Js.Unsafe.any => Js.Unsafe.any =
pattern => {
let revFn = Js.Unsafe.js_expr("window.rev");
Js.Unsafe.fun_call(revFn, [|Js.Unsafe.inject(pattern)|]);
};
let jux: (Js.Unsafe.any, Js.Unsafe.any => Js.Unsafe.any) => Js.Unsafe.any =
(pattern, f) => {
Js.Unsafe.meth_call(pattern, "jux", [|Js.Unsafe.inject(f)|]);
};
let play: Js.Unsafe.any => unit =
pattern => {
Js.Unsafe.meth_call(pattern, "play", [||]);
};
/* Wrapper function to chain methods */
let playNote: string => unit =
pattern => {
let n = note(pattern);
let j = jux(n, rev);
play(j);
};
/* Example usage function */
let exampleUse: unit => unit =
() => {
initStrudel();
playNote("<c a f e>(3,8)");
};
/* Function to stop the music */
let stopMusic: unit => unit = () => hush();
/* Function to initialize Strudel when the DOM is loaded */
let initOnLoad: unit => unit =
() => {
let addEventListenerFn = Js.Unsafe.js_expr("window.addEventListener");
Js.Unsafe.fun_call(
addEventListenerFn,
[|
Js.Unsafe.inject(Js.string("DOMContentLoaded")),
Js.Unsafe.inject(Js.wrap_callback(_ => initStrudel())),
|],
);
};
let initStrudel: unit => unit =
() => Js.Unsafe.coerce(Dom_html.window)##initStrudel();
let hush: unit => unit =
() => {
Js.Unsafe.coerce(Dom_html.window)##hush();
};
let note: string => Js.Unsafe.any =
pattern => {
Js.Unsafe.coerce(Dom_html.window)##note(Js.string(pattern));
};
let rev: Js.Unsafe.any => Js.Unsafe.any =
pattern => {
Js.Unsafe.coerce(Dom_html.window)##rev(pattern);
};
let jux: (Js.Unsafe.any, Js.Unsafe.any => Js.Unsafe.any) => Js.Unsafe.any =
(pattern, f) => {
Js.Unsafe.coerce(pattern)##jux(f);
};
let play: Js.Unsafe.any => unit =
pattern => {
Js.Unsafe.coerce(pattern)##play();
};
/* Wrapper function to chain methods */
let playNote: string => unit =
pattern => {
let n = note(pattern);
let j = jux(n, rev);
play(j);
};
/* Example usage function */
let exampleUse: unit => unit =
() => {
initStrudel();
playNote("<c a f e>(3,8)");
};
/* Function to stop the music */
let stopMusic: unit => unit = () => hush();
/* Function to initialize Strudel when the DOM is loaded */
let initOnLoad: unit => Dom_events.listener =
() => {
Dom_events.listen(
Dom_html.window,
Dom_events.Typ.domContentLoaded,
(_: Js.t(Dom_html.window), _: Js.t(Dom_html.event)) => {
initStrudel();
false; // I don't think this does anything.
},
);
};

Copy link
Contributor

Choose a reason for hiding this comment

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

The false being returned on the Dom_events.listen looks like it would be used for stop propagation but I looked through the js_of_ocaml implementation and I don't see where this is happening.

Copy link
Contributor

Choose a reason for hiding this comment

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

I take it back https://github.com/ocsigen/js_of_ocaml/blob/ea51f5e6e1c54a13eb917d081c51175814b8b58b/lib/js_of_ocaml/dom_events.ml#L32 the newest version calls full_handler which does prevent_default so probably set that to true

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants