Skip to content

Commit

Permalink
Freeverb example
Browse files Browse the repository at this point in the history
  • Loading branch information
cesaref committed Mar 5, 2024
1 parent 0392e1c commit 67aa00b
Show file tree
Hide file tree
Showing 12 changed files with 3,442 additions and 0 deletions.
588 changes: 588 additions & 0 deletions Freeverb/Freeverb.js

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions Freeverb/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
### Auto-generated HTML & Javascript for Cmajor Patch "Freeverb"

This folder contains some self-contained HTML/Javascript files that play and show a Cmajor
patch using WebAssembly and WebAudio.

For `index.html` to display correctly, this folder needs to be served as HTTP, so if you're
running it locally, you'll need to start a webserver that serves this folder, and then
point your browser at whatever URL your webserver provides. For example, you could run
`python3 -m http.server` in this folder, and then browse to the address it chooses.

The files have all been generated using the Cmajor command-line tool:
```
cmaj generate --target=webaudio --output=<location of this folder> <path to the .cmajorpatch file to convert>
```

- `index.html` is a minimal page that creates the javascript object that implements the patch,
connects it to the default audio and MIDI devices, and displays its view.
- `Freeverb.js` - this is the Javascript wrapper class for the patch, encapsulating its
DSP as webassembly, and providing an API that is used to both render the audio and
control its properties.
- `cmaj_api` - this folder contains javascript helper modules and resources.

To learn more about Cmajor, visit [cmajor.dev](cmajor.dev)
7 changes: 7 additions & 0 deletions Freeverb/cmaj_api/assets/cmajor-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
112 changes: 112 additions & 0 deletions Freeverb/cmaj_api/cmaj-event-listener-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// ,ad888ba, 88
// d8"' "8b
// d8 88,dba,,adba, ,aPP8A.A8 88
// Y8, 88 88 88 88 88 88
// Y8a. .a8P 88 88 88 88, ,88 88 (C)2024 Cmajor Software Ltd
// '"Y888Y"' 88 88 88 '"8bbP"Y8 88 https://cmajor.dev
// ,88
// 888P"
//
// This file may be used under the terms of the ISC license:
//
// Permission to use, copy, modify, and/or distribute this software for any purpose with or
// without fee is hereby granted, provided that the above copyright notice and this permission
// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.


/** This event listener management class allows listeners to be attached and
* removed from named event types.
*/
export class EventListenerList
{
constructor()
{
this.listenersPerType = {};
}

/** Adds a listener for a specifc event type.
* If the listener is already registered, this will simply add it again.
* Each call to addEventListener() must be paired with a removeventListener()
* call to remove it.
*
* @param {string} type
*/
addEventListener (type, listener)
{
if (type && listener)
{
const list = this.listenersPerType[type];

if (list)
list.push (listener);
else
this.listenersPerType[type] = [listener];
}
}

/** Removes a listener that was previously added for the given event type.
* @param {string} type
*/
removeEventListener (type, listener)
{
if (type && listener)
{
const list = this.listenersPerType[type];

if (list)
{
const i = list.indexOf (listener);

if (i >= 0)
list.splice (i, 1);
}
}
}

/** Attaches a callback function that will be automatically unregistered
* the first time it is invoked.
*
* @param {string} type
*/
addSingleUseListener (type, listener)
{
const l = message =>
{
this.removeEventListener (type, l);
listener?.(message);
};

this.addEventListener (type, l);
}

/** Synchronously dispatches an event object to all listeners
* that are registered for the given type.
*
* @param {string} type
*/
dispatchEvent (type, event)
{
const list = this.listenersPerType[type];

if (list)
for (const listener of list)
listener?.(event);
}

/** Returns the number of listeners that are currently registered
* for the given type of event.
*
* @param {string} type
*/
getNumListenersForType (type)
{
const list = this.listenersPerType[type];
return list ? list.length : 0;
}
}
178 changes: 178 additions & 0 deletions Freeverb/cmaj_api/cmaj-generic-patch-view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
//
// ,ad888ba, 88
// d8"' "8b
// d8 88,dba,,adba, ,aPP8A.A8 88
// Y8, 88 88 88 88 88 88
// Y8a. .a8P 88 88 88 88, ,88 88 (C)2024 Cmajor Software Ltd
// '"Y888Y"' 88 88 88 '"8bbP"Y8 88 https://cmajor.dev
// ,88
// 888P"
//
// This file may be used under the terms of the ISC license:
//
// Permission to use, copy, modify, and/or distribute this software for any purpose with or
// without fee is hereby granted, provided that the above copyright notice and this permission
// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

import * as Controls from "/cmaj_api/cmaj-parameter-controls.js"

//==============================================================================
/** A simple, generic view which can control any type of patch */
class GenericPatchView extends HTMLElement
{
/** Creates a view for a patch.
* @param {PatchConnection} patchConnection - the connection to the target patch
*/
constructor (patchConnection)
{
super();

this.patchConnection = patchConnection;

this.statusListener = status =>
{
this.status = status;
this.createControlElements();
};

this.attachShadow ({ mode: "open" });
this.shadowRoot.innerHTML = this.getHTML();

this.titleElement = this.shadowRoot.getElementById ("patch-title");
this.parametersElement = this.shadowRoot.getElementById ("patch-parameters");
}

//==============================================================================
/** @private */
connectedCallback()
{
this.patchConnection.addStatusListener (this.statusListener);
this.patchConnection.requestStatusUpdate();
}

/** @private */
disconnectedCallback()
{
this.patchConnection.removeStatusListener (this.statusListener);
}

/** @private */
createControlElements()
{
this.parametersElement.innerHTML = "";
this.titleElement.innerText = this.status?.manifest?.name ?? "Cmajor";

for (const endpointInfo of this.status?.details?.inputs)
{
if (! endpointInfo.annotation?.hidden)
{
const control = Controls.createLabelledControl (this.patchConnection, endpointInfo);

if (control)
this.parametersElement.appendChild (control);
}
}
}

/** @private */
getHTML()
{
return `
<style>
* {
box-sizing: border-box;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
font-family: Avenir, 'Avenir Next LT Pro', Montserrat, Corbel, 'URW Gothic', source-sans-pro, sans-serif;
font-size: 0.9rem;
}
:host {
--header-height: 2.5rem;
--foreground: #ffffff;
--background: #1a1a1a;
display: block;
height: 100%;
background-color: var(--background);
}
.main {
background: var(--background);
height: 100%;
}
.header {
width: 100%;
height: var(--header-height);
border-bottom: 0.1rem solid var(--foreground);
display: flex;
justify-content: space-between;
align-items: center;
}
#patch-title {
color: var(--foreground);
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
cursor: default;
font-size: 140%;
}
.logo {
flex: 1;
height: 80%;
margin-left: 0.3rem;
margin-right: 0.3rem;
background-color: var(--foreground);
mask: url(cmaj_api/assets/cmajor-logo.svg);
mask-repeat: no-repeat;
-webkit-mask: url(cmaj_api/assets/cmajor-logo.svg);
-webkit-mask-repeat: no-repeat;
min-width: 6.25rem;
}
.header-filler {
flex: 1;
}
#patch-parameters {
height: calc(100% - var(--header-height));
overflow: auto;
padding: 1rem;
text-align: center;
}
${Controls.getAllCSS()}
</style>
<div class="main">
<div class="header">
<span class="logo"></span>
<h2 id="patch-title"></h2>
<div class="header-filler"></div>
</div>
<div id="patch-parameters"></div>
</div>`;
}
}

window.customElements.define ("cmaj-generic-patch-view", GenericPatchView);

//==============================================================================
/** Creates a generic view element which can be used to control any patch.
* @param {PatchConnection} patchConnection - the connection to the target patch
*/
export default function createPatchView (patchConnection)
{
return new GenericPatchView (patchConnection);
}
Loading

0 comments on commit 67aa00b

Please sign in to comment.