create database wavtool_test;

\c wavtool_test

--
--


SELECT pg_catalog.set_config('search_path', '', false);

--
-- Name: category; Type: TYPE; Schema: public; Owner: -
--

CREATE TYPE public.category AS ENUM (
    'Edit',
    'Effect',
    'Generator'
);


--
-- Name: user_environment; Type: TYPE; Schema: public; Owner: -
--

CREATE TYPE public.user_environment AS ENUM (
    'production',
    'staging',
    'beta'
);


--
-- Name: user_role; Type: TYPE; Schema: public; Owner: -
--

CREATE TYPE public.user_role AS ENUM (
    'waitlist',
    'standard',
    'admin'
);




--
-- Name: activity; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.activity (
    id integer NOT NULL,
    user_id integer,
    action text NOT NULL,
    "timestamp" timestamp without time zone DEFAULT now() NOT NULL,
    ip text
);


--
-- Name: activity_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.activity ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.activity_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: composer_choices; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.composer_choices (
    id integer NOT NULL,
    request_id integer NOT NULL,
    choice jsonb NOT NULL,
    "timestamp" timestamp without time zone DEFAULT now() NOT NULL
);


--
-- Name: composer_choices_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.composer_choices ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.composer_choices_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: composer_requests; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.composer_requests (
    id integer NOT NULL,
    user_id integer NOT NULL,
    "timestamp" timestamp without time zone DEFAULT now() NOT NULL,
    queue_name text NOT NULL,
    request jsonb NOT NULL,
    response jsonb NOT NULL,
    response_time_ms integer NOT NULL,
    uuid text DEFAULT gen_random_uuid() NOT NULL
);


--
-- Name: composer_requests_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.composer_requests ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.composer_requests_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: conductor_request_tags; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.conductor_request_tags (
    id integer NOT NULL,
    conductor_request_id integer NOT NULL,
    category text NOT NULL,
    value text NOT NULL,
    tag_version text
);


--
-- Name: conductor_request_tags_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.conductor_request_tags ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.conductor_request_tags_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: conductor_requests; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.conductor_requests (
    id integer NOT NULL,
    user_id integer,
    request text,
    context json,
    response json,
    response_time_ms integer,
    "timestamp" timestamp without time zone DEFAULT now(),
    tag_version text,
    session_context json
);


--
-- Name: conductor_requests_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.conductor_requests ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.conductor_requests_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: conductor_token_quota; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.conductor_token_quota (
    minute integer NOT NULL,
    tokens_used integer NOT NULL
);


--
-- Name: config_vals; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.config_vals (
    id integer NOT NULL,
    name text,
    value text
);


--
-- Name: config_vals_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.config_vals ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.config_vals_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: device_tags; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.device_tags (
    id integer NOT NULL,
    device_id integer NOT NULL,
    value text NOT NULL,
    category text
);


--
-- Name: device_tags_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.device_tags ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.device_tags_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: devices; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.devices (
    id integer NOT NULL,
    name text NOT NULL,
    author_id integer NOT NULL,
    code text NOT NULL,
    superseded_by integer,
    "timestamp" timestamp without time zone DEFAULT now() NOT NULL,
    uuid text DEFAULT gen_random_uuid() NOT NULL
);


--
-- Name: devices_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.devices ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.devices_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: ecs_scalers; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.ecs_scalers (
    id integer NOT NULL,
    name text NOT NULL,
    cluster_arn text NOT NULL,
    service_arn text NOT NULL,
    min_workers integer NOT NULL,
    max_workers integer NOT NULL,
    cooldown_seconds integer NOT NULL,
    last_scale_time timestamp without time zone,
    CONSTRAINT ecs_scalers_check CHECK (((min_workers >= 0) AND (min_workers <= max_workers) AND (cooldown_seconds >= 0)))
);


--
-- Name: ecs_scalers_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.ecs_scalers ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.ecs_scalers_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: embedded_strings; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.embedded_strings (
    id integer NOT NULL,
    content text NOT NULL,
    embedding json NOT NULL
);


--
-- Name: embedded_strings_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.embedded_strings ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.embedded_strings_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: migrations; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.migrations (
    id integer NOT NULL,
    filename text NOT NULL,
    "timestamp" timestamp without time zone DEFAULT now() NOT NULL,
    content text NOT NULL
);


--
-- Name: preset_tags; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.preset_tags (
    id integer NOT NULL,
    preset_id integer NOT NULL,
    value text NOT NULL,
    category text
);


--
-- Name: preset_tags_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.preset_tags ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.preset_tags_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: presets; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.presets (
    id integer NOT NULL,
    name text NOT NULL,
    settings json NOT NULL,
    device_id integer NOT NULL,
    superseded_by integer,
    "timestamp" timestamp without time zone DEFAULT now() NOT NULL,
    author_id integer NOT NULL,
    uuid text DEFAULT gen_random_uuid() NOT NULL
);


--
-- Name: presets_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.presets ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.presets_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: sample_packs; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.sample_packs (
    id integer NOT NULL,
    name text NOT NULL,
    author_id integer,
    uuid text DEFAULT gen_random_uuid() NOT NULL,
    "timestamp" timestamp without time zone DEFAULT now() NOT NULL,
    published boolean DEFAULT false
);


--
-- Name: sample_packs_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.sample_packs ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.sample_packs_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: sample_tags; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.sample_tags (
    id integer NOT NULL,
    sample_id integer NOT NULL,
    value text NOT NULL,
    category text
);


--
-- Name: sample_tags_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.sample_tags ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.sample_tags_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: samples; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.samples (
    id integer NOT NULL,
    name text NOT NULL,
    s3_key text NOT NULL,
    length_in_ms integer,
    pack_id integer,
    author_id integer,
    uuid text DEFAULT gen_random_uuid() NOT NULL,
    waveform_svg text,
    warp json,
    "timestamp" timestamp without time zone DEFAULT now() NOT NULL,
    bpm integer
);


--
-- Name: samples_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.samples ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.samples_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: shared_tracks_plays; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.shared_tracks_plays (
    id integer NOT NULL,
    user_id integer,
    track_id integer NOT NULL
);


--
-- Name: shared_tracks_plays_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.shared_tracks_plays ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.shared_tracks_plays_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: signup_surveys; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.signup_surveys (
    id integer NOT NULL,
    user_id integer NOT NULL,
    answers json NOT NULL
);


--
-- Name: signup_surveys_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.signup_surveys ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.signup_surveys_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: subscription_updates; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.subscription_updates (
    id integer NOT NULL,
    user_id integer NOT NULL,
    subscribed_until timestamp without time zone NOT NULL,
    amount_paid_cents_usd integer DEFAULT 0 NOT NULL,
    "timestamp" timestamp without time zone DEFAULT now(),
    will_renew boolean DEFAULT true
);


--
-- Name: subscription_updates_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.subscription_updates ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.subscription_updates_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: tracks_for_sharing; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.tracks_for_sharing (
    id integer NOT NULL,
    s3_key text NOT NULL,
    author_id integer NOT NULL,
    name text NOT NULL,
    uuid text NOT NULL,
    s3_url text NOT NULL,
    band_energy jsonb NOT NULL
);


--
-- Name: tracks_for_sharing_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.tracks_for_sharing ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.tracks_for_sharing_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: user_identities; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.user_identities (
    id integer NOT NULL,
    user_id integer NOT NULL,
    issuer text NOT NULL,
    external_id text NOT NULL,
    profile_data text
);


--
-- Name: user_identities_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.user_identities ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.user_identities_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: users; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.users (
    id integer NOT NULL,
    normalized_email text NOT NULL,
    email text NOT NULL,
    display_name text NOT NULL,
    role public.user_role DEFAULT 'standard'::public.user_role NOT NULL,
    stripe_customer_id text,
    message_limit_override integer,
    environment public.user_environment DEFAULT 'production'::public.user_environment NOT NULL,
    share_name text,
    user_categorisation text
);


--
-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.users ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.users_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Data for Name: activity; Type: TABLE DATA; Schema: public; Owner: -
--

COPY public.activity (id, user_id, action, "timestamp", ip) FROM stdin;
342740	76	login	2023-08-04 10:38:30.718642	\N
\.


--
-- Data for Name: composer_choices; Type: TABLE DATA; Schema: public; Owner: -
--

COPY public.composer_choices (id, request_id, choice, "timestamp") FROM stdin;
13095	37339	[{"end": 4, "pitch": 64, "start": 0, "lifted": true, "velocity": 0.5905511811023622}, {"end": 4, "pitch": 69, "start": 0, "lifted": true, "velocity": 0.5905511811023622}, {"end": 4, "pitch": 73, "start": 0, "lifted": true, "velocity": 0.5905511811023622}, {"end": 8, "pitch": 62, "start": 4, "lifted": true, "velocity": 0.5905511811023622}, {"end": 8, "pitch": 66, "start": 4, "lifted": true, "velocity": 0.5905511811023622}, {"end": 8, "pitch": 69, "start": 4, "lifted": true, "velocity": 0.5905511811023622}, {"end": 12, "pitch": 64, "start": 8, "lifted": true, "velocity": 0.5905511811023622}, {"end": 12, "pitch": 69, "start": 8, "lifted": true, "velocity": 0.5905511811023622}, {"end": 12, "pitch": 73, "start": 8, "lifted": true, "velocity": 0.5905511811023622}, {"end": 16, "pitch": 62, "start": 12, "lifted": true, "velocity": 0.5905511811023622}, {"end": 16, "pitch": 66, "start": 12, "lifted": true, "velocity": 0.5905511811023622}, {"end": 16, "pitch": 69, "start": 12, "lifted": true, "velocity": 0.5905511811023622}]	2023-08-04 10:42:42.58616
\.


--
-- Data for Name: composer_requests; Type: TABLE DATA; Schema: public; Owner: -
--

COPY public.composer_requests (id, user_id, "timestamp", queue_name, request, response, response_time_ms, uuid) FROM stdin;
37336	76	2023-08-04 10:38:42.801188	composer_requests	{"beams": 1, "notes": [], "requests": [{"bias": {"pitch >= 1000": -1000}, "dists": false, "temperature": 0.6000000000000001, "maxPolyphony": 10, "minPolyphony": 3, "stopCondition": "error || beat >= 16  ||beat >= 16 || (steps >= 4 && (beat >= 4 || maxProbability < 0.2))"}, {"bias": {"pitch >= 1000": -1000}, "dists": false, "temperature": 0.8, "maxPolyphony": 10, "minPolyphony": 3, "stopCondition": "error || beat >= 16  ||beat >= 16 || (steps >= 4 && (beat >= 4 || maxProbability < 0.2))"}, {"bias": {"pitch >= 1000": -1000}, "dists": false, "temperature": 1, "maxPolyphony": 10, "minPolyphony": 3, "stopCondition": "error || beat >= 16  ||beat >= 16 || (steps >= 4 && (beat >= 4 || maxProbability < 0.2))"}], "accompany": []}	{"response": [{"notes": [{"end": 4, "pitch": 61, "start": 0, "lifted": true, "velocity": 0.7086614173228346}, {"end": 4, "pitch": 65, "start": 0, "lifted": true, "velocity": 0.7086614173228346}, {"end": 4, "pitch": 73, "start": 0, "lifted": true, "velocity": 0.7086614173228346}, {"end": 6, "pitch": 60, "start": 4, "lifted": true, "velocity": 0.7086614173228346}, {"end": 6, "pitch": 63, "start": 4, "lifted": true, "velocity": 0.7086614173228346}, {"end": 6, "pitch": 72, "start": 4, "lifted": true, "velocity": 0.7086614173228346}, {"end": 8, "pitch": 61, "start": 6, "lifted": true, "velocity": 0.7086614173228346}, {"end": 8, "pitch": 65, "start": 6, "lifted": true, "velocity": 0.7086614173228346}, {"end": 8, "pitch": 73, "start": 6, "lifted": true, "velocity": 0.7086614173228346}, {"end": 12, "pitch": 63, "start": 8, "lifted": true, "velocity": 0.7086614173228346}, {"end": 12, "pitch": 66, "start": 8, "lifted": true, "velocity": 0.7086614173228346}, {"end": 12, "pitch": 75, "start": 8, "lifted": true, "velocity": 0.7086614173228346}]}, {"notes": [{"end": 0.5416666666666666, "pitch": 53, "start": 0, "lifted": true, "velocity": 0.47244094488188976}, {"end": 0.5416666666666666, "pitch": 59, "start": 0, "lifted": true, "velocity": 0.47244094488188976}, {"end": 0.5416666666666666, "pitch": 62, "start": 0, "lifted": true, "velocity": 0.47244094488188976}, {"end": 0.9166666666666666, "pitch": 53, "start": 0.6666666666666666, "lifted": true, "velocity": 0.47244094488188976}, {"end": 0.9166666666666666, "pitch": 59, "start": 0.6666666666666666, "lifted": true, "velocity": 0.47244094488188976}, {"end": 0.9166666666666666, "pitch": 62, "start": 0.6666666666666666, "lifted": true, "velocity": 0.47244094488188976}, {"end": 3.5833333333333335, "pitch": 53, "start": 1.6666666666666667, "lifted": true, "velocity": 0.5511811023622047}, {"end": 3.5833333333333335, "pitch": 59, "start": 1.6666666666666667, "lifted": true, "velocity": 0.5511811023622047}, {"end": 3.5833333333333335, "pitch": 62, "start": 1.6666666666666667, "lifted": true, "velocity": 0.5511811023622047}, {"end": 4, "pitch": 53, "start": 3.6666666666666665, "lifted": true, "velocity": 0.47244094488188976}, {"end": 4, "pitch": 59, "start": 3.6666666666666665, "lifted": true, "velocity": 0.47244094488188976}, {"end": 4, "pitch": 62, "start": 3.6666666666666665, "lifted": true, "velocity": 0.47244094488188976}]}, {"notes": [{"end": 0.3333333333333333, "pitch": 52, "start": 0, "lifted": true, "velocity": 0.47244094488188976}, {"end": 0.3333333333333333, "pitch": 56, "start": 0, "lifted": true, "velocity": 0.5118110236220472}, {"end": 0.3333333333333333, "pitch": 59, "start": 0, "lifted": true, "velocity": 0.5118110236220472}, {"end": 0.6666666666666666, "pitch": 52, "start": 0.3333333333333333, "lifted": true, "velocity": 0.5905511811023622}, {"end": 0.6666666666666666, "pitch": 56, "start": 0.3333333333333333, "lifted": true, "velocity": 0.6692913385826772}, {"end": 0.6666666666666666, "pitch": 59, "start": 0.3333333333333333, "lifted": true, "velocity": 0.7086614173228346}, {"end": 1, "pitch": 52, "start": 0.6666666666666666, "lifted": true, "velocity": 0.5905511811023622}, {"end": 1, "pitch": 56, "start": 0.6666666666666666, "lifted": true, "velocity": 0.5905511811023622}, {"end": 1, "pitch": 59, "start": 0.6666666666666666, "lifted": true, "velocity": 0.5905511811023622}, {"end": 1.3333333333333333, "pitch": 52, "start": 1, "lifted": true, "velocity": 0.7874015748031497}, {"end": 1.3333333333333333, "pitch": 56, "start": 1, "lifted": true, "velocity": 0.7480314960629921}, {"end": 1.3333333333333333, "pitch": 59, "start": 1, "lifted": true, "velocity": 0.8267716535433071}]}]}	1343	04760d6c-281f-4041-a55a-90a2b64b30d3
37337	76	2023-08-04 10:39:00.410811	composer_requests	{"beams": 1, "notes": [], "requests": [{"bias": {"pitch >= 1000": -1000}, "dists": false, "temperature": 0.6000000000000001, "maxPolyphony": 10, "minPolyphony": 3, "stopCondition": "error || beat >= 16  ||beat >= 16 || (steps >= 4 && (beat >= 4 || maxProbability < 0.2))"}, {"bias": {"pitch >= 1000": -1000}, "dists": false, "temperature": 0.8, "maxPolyphony": 10, "minPolyphony": 3, "stopCondition": "error || beat >= 16  ||beat >= 16 || (steps >= 4 && (beat >= 4 || maxProbability < 0.2))"}, {"bias": {"pitch >= 1000": -1000}, "dists": false, "temperature": 1, "maxPolyphony": 10, "minPolyphony": 3, "stopCondition": "error || beat >= 16  ||beat >= 16 || (steps >= 4 && (beat >= 4 || maxProbability < 0.2))"}], "accompany": []}	{"response": [{"notes": [{"end": 0.625, "pitch": 58, "start": 0, "lifted": true, "velocity": 0.5118110236220472}, {"end": 0.625, "pitch": 63, "start": 0, "lifted": true, "velocity": 0.5118110236220472}, {"end": 0.625, "pitch": 67, "start": 0, "lifted": true, "velocity": 0.5118110236220472}, {"end": 1, "pitch": 58, "start": 0.6666666666666666, "lifted": true, "velocity": 0.5118110236220472}, {"end": 1, "pitch": 63, "start": 0.6666666666666666, "lifted": true, "velocity": 0.5118110236220472}, {"end": 1, "pitch": 67, "start": 0.6666666666666666, "lifted": true, "velocity": 0.5118110236220472}, {"end": 2, "pitch": 58, "start": 1.6666666666666667, "lifted": true, "velocity": 0.5118110236220472}, {"end": 2, "pitch": 63, "start": 1.6666666666666667, "lifted": true, "velocity": 0.5118110236220472}, {"end": 2, "pitch": 67, "start": 1.6666666666666667, "lifted": true, "velocity": 0.5118110236220472}, {"end": 3, "pitch": 58, "start": 2.6666666666666665, "lifted": true, "velocity": 0.5118110236220472}, {"end": 3, "pitch": 63, "start": 2.6666666666666665, "lifted": true, "velocity": 0.5118110236220472}, {"end": 3, "pitch": 67, "start": 2.6666666666666665, "lifted": true, "velocity": 0.5118110236220472}, {"end": 4, "pitch": 58, "start": 3.6666666666666665, "lifted": true, "velocity": 0.5118110236220472}, {"end": 4, "pitch": 63, "start": 3.6666666666666665, "lifted": true, "velocity": 0.5118110236220472}, {"end": 4, "pitch": 67, "start": 3.6666666666666665, "lifted": true, "velocity": 0.5118110236220472}]}, {"notes": [{"end": 1.5, "pitch": 36, "start": 0, "lifted": true, "velocity": 0.6299212598425197}, {"end": 4, "pitch": 40, "start": 0, "lifted": true, "velocity": 0.7086614173228346}, {"end": 0.5, "pitch": 46, "start": 0, "lifted": true, "velocity": 0.7086614173228346}, {"end": 0.5833333333333334, "pitch": 46, "start": 0.5, "lifted": true, "velocity": 0.6299212598425197}, {"end": 1, "pitch": 52, "start": 0.5, "lifted": true, "velocity": 0.6299212598425197}, {"end": 1, "pitch": 58, "start": 0.5, "lifted": true, "velocity": 0.7480314960629921}, {"end": 1, "pitch": 36, "start": 0.7916666666666666, "lifted": true, "velocity": 0.5118110236220472}, {"end": 0.875, "pitch": 46, "start": 0.7916666666666666, "lifted": true, "velocity": 0.5118110236220472}, {"end": 1, "pitch": 48, "start": 0.7916666666666666, "lifted": true, "velocity": 0.5905511811023622}, {"end": 1.5, "pitch": 36, "start": 1, "lifted": true, "velocity": 0.5905511811023622}, {"end": 1.5, "pitch": 48, "start": 1, "lifted": true, "velocity": 0.5905511811023622}, {"end": 1.5, "pitch": 60, "start": 1, "lifted": true, "velocity": 0.7086614173228346}, {"end": 2.5, "pitch": 36, "start": 1.5, "lifted": true, "velocity": 0.6299212598425197}, {"end": 2.5, "pitch": 48, "start": 1.5, "lifted": true, "velocity": 0.6299212598425197}]}, {"notes": [{"end": 0.3333333333333333, "pitch": 58, "start": 0, "lifted": true, "velocity": 0.8661417322834646}, {"end": 0.3333333333333333, "pitch": 63, "start": 0, "lifted": true, "velocity": 0.7874015748031497}, {"end": 0.4166666666666667, "pitch": 68, "start": 0, "lifted": true, "velocity": 0.8267716535433071}, {"end": 0.875, "pitch": 58, "start": 0.5, "lifted": true, "velocity": 0.8661417322834646}, {"end": 0.9583333333333334, "pitch": 63, "start": 0.5, "lifted": true, "velocity": 0.7086614173228346}, {"end": 1.3333333333333333, "pitch": 67, "start": 0.5, "lifted": true, "velocity": 0.7086614173228346}, {"end": 2.25, "pitch": 58, "start": 1.5, "lifted": true, "velocity": 0.8661417322834646}, {"end": 2.375, "pitch": 63, "start": 1.5, "lifted": true, "velocity": 0.7480314960629921}, {"end": 2.4166666666666665, "pitch": 67, "start": 1.5, "lifted": true, "velocity": 0.8267716535433071}, {"end": 3.1666666666666665, "pitch": 58, "start": 2.5, "lifted": true, "velocity": 0.8267716535433071}, {"end": 3.25, "pitch": 63, "start": 2.5, "lifted": true, "velocity": 0.7480314960629921}, {"end": 3.25, "pitch": 67, "start": 2.5, "lifted": true, "velocity": 0.8267716535433071}]}]}	1386	b0343c73-4f74-45aa-9eeb-8339b91621be
37338	76	2023-08-04 10:39:13.313705	composer_requests	{"beams": 1, "notes": [], "requests": [{"bias": {"pitch >= 1000": -1000}, "dists": false, "temperature": 0.6000000000000001, "maxPolyphony": 10, "minPolyphony": 3, "stopCondition": "error || beat >= 16  ||beat >= 16 || (steps >= 4 && (beat >= 4 || maxProbability < 0.2))"}, {"bias": {"pitch >= 1000": -1000}, "dists": false, "temperature": 0.8, "maxPolyphony": 10, "minPolyphony": 3, "stopCondition": "error || beat >= 16  ||beat >= 16 || (steps >= 4 && (beat >= 4 || maxProbability < 0.2))"}, {"bias": {"pitch >= 1000": -1000}, "dists": false, "temperature": 1, "maxPolyphony": 10, "minPolyphony": 3, "stopCondition": "error || beat >= 16  ||beat >= 16 || (steps >= 4 && (beat >= 4 || maxProbability < 0.2))"}], "accompany": []}	{"response": [{"notes": [{"end": 2, "pitch": 57, "start": 0, "lifted": true, "velocity": 0.7086614173228346}, {"end": 2, "pitch": 61, "start": 0, "lifted": true, "velocity": 0.7086614173228346}, {"end": 2, "pitch": 64, "start": 0, "lifted": true, "velocity": 0.7086614173228346}, {"end": 4, "pitch": 57, "start": 2, "lifted": true, "velocity": 0.7086614173228346}, {"end": 4, "pitch": 61, "start": 2, "lifted": true, "velocity": 0.7086614173228346}, {"end": 4, "pitch": 66, "start": 2, "lifted": true, "velocity": 0.7086614173228346}, {"end": 6, "pitch": 56, "start": 4, "lifted": true, "velocity": 0.7086614173228346}, {"end": 6, "pitch": 59, "start": 4, "lifted": true, "velocity": 0.7086614173228346}, {"end": 6, "pitch": 64, "start": 4, "lifted": true, "velocity": 0.7086614173228346}, {"end": 8, "pitch": 56, "start": 6, "lifted": true, "velocity": 0.7086614173228346}, {"end": 8, "pitch": 59, "start": 6, "lifted": true, "velocity": 0.7086614173228346}, {"end": 8, "pitch": 64, "start": 6, "lifted": true, "velocity": 0.7086614173228346}]}, {"notes": [{"end": 1.6666666666666667, "pitch": 57, "start": 0, "lifted": true, "velocity": 0.6692913385826772}, {"end": 1.6666666666666667, "pitch": 60, "start": 0, "lifted": true, "velocity": 0.6692913385826772}, {"end": 1.6666666666666667, "pitch": 65, "start": 0, "lifted": true, "velocity": 0.6692913385826772}, {"end": 2.5416666666666665, "pitch": 55, "start": 2, "lifted": true, "velocity": 0.6692913385826772}, {"end": 2.5416666666666665, "pitch": 60, "start": 2, "lifted": true, "velocity": 0.6692913385826772}, {"end": 2.5416666666666665, "pitch": 64, "start": 2, "lifted": true, "velocity": 0.6692913385826772}, {"end": 4, "pitch": 55, "start": 2.6666666666666665, "lifted": true, "velocity": 0.6692913385826772}, {"end": 4, "pitch": 60, "start": 2.6666666666666665, "lifted": true, "velocity": 0.6692913385826772}, {"end": 4, "pitch": 64, "start": 2.6666666666666665, "lifted": true, "velocity": 0.6692913385826772}, {"end": 5.75, "pitch": 57, "start": 4, "lifted": true, "velocity": 0.6692913385826772}, {"end": 5.75, "pitch": 60, "start": 4, "lifted": true, "velocity": 0.6692913385826772}, {"end": 5.75, "pitch": 65, "start": 4, "lifted": true, "velocity": 0.6692913385826772}]}, {"notes": [{"end": 0.7083333333333334, "pitch": 67, "start": 0, "lifted": true, "velocity": 0.8267716535433071}, {"end": 0.7083333333333334, "pitch": 70, "start": 0, "lifted": true, "velocity": 0.8267716535433071}, {"end": 0.75, "pitch": 74, "start": 0, "lifted": true, "velocity": 0.8267716535433071}, {"end": 0.9583333333333334, "pitch": 67, "start": 0.75, "lifted": true, "velocity": 0.8267716535433071}, {"end": 0.9583333333333334, "pitch": 70, "start": 0.75, "lifted": true, "velocity": 0.8267716535433071}, {"end": 0.9583333333333334, "pitch": 74, "start": 0.75, "lifted": true, "velocity": 0.8267716535433071}, {"end": 1.4583333333333333, "pitch": 67, "start": 1, "lifted": true, "velocity": 0.8267716535433071}, {"end": 1.4583333333333333, "pitch": 70, "start": 1, "lifted": true, "velocity": 0.8267716535433071}, {"end": 1.4583333333333333, "pitch": 74, "start": 1, "lifted": true, "velocity": 0.8267716535433071}, {"end": 1.9583333333333333, "pitch": 67, "start": 1.5, "lifted": true, "velocity": 0.8267716535433071}, {"end": 1.9583333333333333, "pitch": 70, "start": 1.5, "lifted": true, "velocity": 0.8267716535433071}, {"end": 1.9583333333333333, "pitch": 74, "start": 1.5, "lifted": true, "velocity": 0.8267716535433071}]}]}	1155	755e944d-96f9-40fb-8665-2c5be9b80536
37339	76	2023-08-04 10:42:40.743259	composer_requests	{"beams": 1, "notes": [], "requests": [{"bias": {"pitch >= 1000": -1000}, "dists": false, "temperature": 0.6000000000000001, "maxPolyphony": 10, "minPolyphony": 3, "stopCondition": "error || beat >= 16  ||beat >= 16 || (steps >= 4 && (beat >= 4 || maxProbability < 0.2))"}, {"bias": {"pitch >= 1000": -1000}, "dists": false, "temperature": 0.8, "maxPolyphony": 10, "minPolyphony": 3, "stopCondition": "error || beat >= 16  ||beat >= 16 || (steps >= 4 && (beat >= 4 || maxProbability < 0.2))"}, {"bias": {"pitch >= 1000": -1000}, "dists": false, "temperature": 1, "maxPolyphony": 10, "minPolyphony": 3, "stopCondition": "error || beat >= 16  ||beat >= 16 || (steps >= 4 && (beat >= 4 || maxProbability < 0.2))"}], "accompany": []}	{"response": [{"notes": [{"end": 4, "pitch": 64, "start": 0, "lifted": true, "velocity": 0.5905511811023622}, {"end": 4, "pitch": 69, "start": 0, "lifted": true, "velocity": 0.5905511811023622}, {"end": 4, "pitch": 73, "start": 0, "lifted": true, "velocity": 0.5905511811023622}, {"end": 8, "pitch": 62, "start": 4, "lifted": true, "velocity": 0.5905511811023622}, {"end": 8, "pitch": 66, "start": 4, "lifted": true, "velocity": 0.5905511811023622}, {"end": 8, "pitch": 69, "start": 4, "lifted": true, "velocity": 0.5905511811023622}, {"end": 12, "pitch": 64, "start": 8, "lifted": true, "velocity": 0.5905511811023622}, {"end": 12, "pitch": 69, "start": 8, "lifted": true, "velocity": 0.5905511811023622}, {"end": 12, "pitch": 73, "start": 8, "lifted": true, "velocity": 0.5905511811023622}, {"end": 16, "pitch": 62, "start": 12, "lifted": true, "velocity": 0.5905511811023622}, {"end": 16, "pitch": 66, "start": 12, "lifted": true, "velocity": 0.5905511811023622}, {"end": 16, "pitch": 69, "start": 12, "lifted": true, "velocity": 0.5905511811023622}]}, {"notes": [{"end": 0.5, "pitch": 54, "start": 0, "lifted": true, "velocity": 0.5511811023622047}, {"end": 0.5, "pitch": 59, "start": 0, "lifted": true, "velocity": 0.5511811023622047}, {"end": 0.5, "pitch": 63, "start": 0, "lifted": true, "velocity": 0.6692913385826772}, {"end": 1, "pitch": 54, "start": 0.5, "lifted": true, "velocity": 0.5118110236220472}, {"end": 1, "pitch": 59, "start": 0.5, "lifted": true, "velocity": 0.5118110236220472}, {"end": 1, "pitch": 63, "start": 0.5, "lifted": true, "velocity": 0.5905511811023622}, {"end": 1.5, "pitch": 54, "start": 1, "lifted": true, "velocity": 0.5118110236220472}, {"end": 1.5, "pitch": 59, "start": 1, "lifted": true, "velocity": 0.5118110236220472}, {"end": 1.5, "pitch": 63, "start": 1, "lifted": true, "velocity": 0.5905511811023622}, {"end": 2, "pitch": 54, "start": 1.5, "lifted": true, "velocity": 0.5118110236220472}, {"end": 2, "pitch": 59, "start": 1.5, "lifted": true, "velocity": 0.5118110236220472}, {"end": 2, "pitch": 63, "start": 1.5, "lifted": true, "velocity": 0.5905511811023622}, {"end": 2.5, "pitch": 54, "start": 2, "lifted": true, "velocity": 0.5511811023622047}, {"end": 2.5, "pitch": 59, "start": 2, "lifted": true, "velocity": 0.5511811023622047}, {"end": 2.5, "pitch": 63, "start": 2, "lifted": true, "velocity": 0.6299212598425197}, {"end": 3, "pitch": 54, "start": 2.5, "lifted": true, "velocity": 0.5118110236220472}, {"end": 3, "pitch": 59, "start": 2.5, "lifted": true, "velocity": 0.5118110236220472}, {"end": 3, "pitch": 63, "start": 2.5, "lifted": true, "velocity": 0.5905511811023622}, {"end": 3.5, "pitch": 54, "start": 3, "lifted": true, "velocity": 0.5118110236220472}, {"end": 3.5, "pitch": 59, "start": 3, "lifted": true, "velocity": 0.5118110236220472}, {"end": 3.5, "pitch": 63, "start": 3, "lifted": true, "velocity": 0.5905511811023622}, {"end": 4, "pitch": 54, "start": 3.5, "lifted": true, "velocity": 0.5118110236220472}, {"end": 4, "pitch": 59, "start": 3.5, "lifted": true, "velocity": 0.5118110236220472}, {"end": 4, "pitch": 63, "start": 3.5, "lifted": true, "velocity": 0.5905511811023622}]}, {"notes": [{"end": 0.4583333333333333, "pitch": 58, "start": 0, "lifted": true, "velocity": 0.7874015748031497}, {"end": 0.4583333333333333, "pitch": 61, "start": 0, "lifted": true, "velocity": 0.7874015748031497}, {"end": 0.4583333333333333, "pitch": 66, "start": 0, "lifted": true, "velocity": 0.7874015748031497}, {"end": 1.4583333333333333, "pitch": 58, "start": 1, "lifted": true, "velocity": 0.7874015748031497}, {"end": 1.4583333333333333, "pitch": 61, "start": 1, "lifted": true, "velocity": 0.7874015748031497}, {"end": 1.4583333333333333, "pitch": 66, "start": 1, "lifted": true, "velocity": 0.7874015748031497}, {"end": 2.4583333333333335, "pitch": 58, "start": 2, "lifted": true, "velocity": 0.7874015748031497}, {"end": 2.4583333333333335, "pitch": 61, "start": 2, "lifted": true, "velocity": 0.7874015748031497}, {"end": 2.4583333333333335, "pitch": 66, "start": 2, "lifted": true, "velocity": 0.7874015748031497}, {"end": 3.4583333333333335, "pitch": 58, "start": 3, "lifted": true, "velocity": 0.7874015748031497}, {"end": 3.4583333333333335, "pitch": 61, "start": 3, "lifted": true, "velocity": 0.7874015748031497}, {"end": 3.4583333333333335, "pitch": 66, "start": 3, "lifted": true, "velocity": 0.7874015748031497}]}]}	1499	cbf27bc4-59a3-4223-8894-4486b6341066
\.


--
-- Data for Name: conductor_request_tags; Type: TABLE DATA; Schema: public; Owner: -
--

COPY public.conductor_request_tags (id, conductor_request_id, category, value, tag_version) FROM stdin;
\.


--
-- Data for Name: conductor_requests; Type: TABLE DATA; Schema: public; Owner: -
--

COPY public.conductor_requests (id, user_id, request, context, response, response_time_ms, "timestamp", tag_version, session_context) FROM stdin;
127629	76	su	{"trackDetailsById":{"TR1":{"name":"Piano","type":"MIDI","drumMachine":false}},"lastCreatedTrackId":null,"focusedTrackId":"TRM","tracksWithTimelineSelection":["MIDITrack-0cd28392-ddfa-48f3-bf43-76935cbdff73"],"timelineSelectionStart":0,"timelineSelectionEnd":16,"bpm":120,"timeSignature":"4/4","lastUpdatedClip":null,"selectedClip":{"notes":[],"selectionStart":0,"selectionEnd":0,"loopStart":0,"loopEnd":16,"timelineStart":0,"trackId":"MIDITrack-0cd28392-ddfa-48f3-bf43-76935cbdff73"},"messageHistory":[]}	{"error":false,"message":"Lo siento, no entiendo lo que estás tratando de decir. ¿Puede decirme en qué puedo ayudarte en el proyecto?","sessionContext":{"sessionId":"fc5a7b26-63c7-4a63-870d-1a476244469e","params":{"useFAQ":true}},"faqAnswers":null,"tokensUsed":{"total":1582,"system":1548,"user":4,"assistant":30,"streamed":30}}	6039	2023-08-04 10:42:35.106732	\N	{"sessionId":"fc5a7b26-63c7-4a63-870d-1a476244469e","params":{"useFAQ":true}}
\.


--
-- Data for Name: conductor_token_quota; Type: TABLE DATA; Schema: public; Owner: -
--

COPY public.conductor_token_quota (minute, tokens_used) FROM stdin;
28186182	1582
\.


--
-- Data for Name: config_vals; Type: TABLE DATA; Schema: public; Owner: -
--

COPY public.config_vals (id, name, value) FROM stdin;
3	conductor-token-quota-free-users	34000
2	conductor-status-severity	info
4	composer_scale_up_threshold	1.0
5	composer_timeout	10.0
1	_conductor-status-message	For a limited time, we're offing 20 free chatbot messages for free users!
\.


--
-- Data for Name: device_tags; Type: TABLE DATA; Schema: public; Owner: -
--

COPY public.device_tags (id, device_id, value, category) FROM stdin;
1	34	Misc	Category
2	3	Utility	Category
3	4	Utility	Category
4	5	Utility	Category
5	7	Utility	Category
6	8	Utility	Category
7	9	Utility	Category
8	10	Utility	Category
9	11	Delay	Category
10	12	Delay	Category
11	13	Dynamics	Category
12	14	Dynamics	Category
13	15	Utility	Category
14	16	EQ	Category
15	17	Dynamics	Category
16	18	Combination	Category
17	19	Reverb	Category
18	20	Dynamics	Category
19	21	Separation	Category
20	22	Separation	Category
21	23	Separation	Category
22	24	Distortion	Category
23	25	Distortion	Category
24	26	Distortion	Category
25	27	Combination	Category
26	30	Synth	Category
27	1	Synth	Category
28	2	Drums	Category
29	28	Drums	Category
30	29	Visualizer	Category
31	31	Visualizer	Category
32	32	Visualizer	Category
33	33	Misc	Category
34	71	Dynamics	Category
35	72	Distortion	Category
36	74	Drums	Category
37	76	Delay	Category
39	78	Reverb	Category
40	79	Synth	Category
41	81	Drums	Category
\.


--
-- Data for Name: devices; Type: TABLE DATA; Schema: public; Owner: -
--

COPY public.devices (id, name, author_id, code, superseded_by, "timestamp", uuid) FROM stdin;
1	Wavetable Synth	1	const WARP_MODES = {\n  Off: WavetableWarpMode.Off,\n  Bend: WavetableWarpMode.Bend,\n  Sync: WavetableWarpMode.Sync,\n}\n\nconst MODULATION_MODES = ['Off', 'FM', 'AM', 'Ring'];\n\nconst SUB_WAVEFORMS = {\n  'Saw': Waveform.Sawtooth,\n  'Sine': Waveform.Sine,\n  'Square': Waveform.Square,\n}\n\nconst FILTER_TYPES = {\n  'Low Pass': BiQuadFilterMode.LowPass,\n  'High Pass': BiQuadFilterMode.HighPass,\n  'Band Pass': BiQuadFilterMode.BandPass,\n  'Notch': BiQuadFilterMode.Notch,\n}\n\nconst usePitchModulator = (pitch, minPitch, maxPitch) => {\n  const result = useMonoSignal();\n  const range = useMonoSignal();\n  subtract(pitch, minPitch, result);\n  subtract(maxPitch, minPitch, range);\n  divide(result, range, result);\n  return result;\n}\n\nconst useVelocityModulator = (velocity, velocityCurve) => {\n  const result = useMonoSignal();\n  for (let i = 0; i < velocity.length; i ++) {\n    result[i] = Math.pow(velocity[i], Math.pow(2, velocityCurve[i] * -2))\n  }\n  return result;\n}\n\nconst useMultipliedEnvelope = (curve, amplitude, trigger, gate, attack, decay, sustain, release) => {\n  return multiply(\n    amplitude,\n    useEnvelope(curve === 'Linear' ? Curve.Linear : Curve.Log2)(trigger, gate, attack, decay, sustain, release),\n    useMonoSignal()\n  );\n}\n\nconst LFO_BEAT_FRACTIONS = [\n  32,\n  24,\n  16,\n  12,\n  8,\n  6,\n  4,\n  3,\n  2,\n  1.5,\n  4/3,\n  1,\n  3/4,\n  2/3,\n  1/2,\n  1/3,\n  1/4,\n  1/6,\n  1/8,\n  1/12,\n  1/16,\n  1/24,\n  1/32\n];\n\nconst useSyncFrequency = (frequency, bpm) => {\n  const syncedFrequency = useMonoSignal(frequency.length);\n  for (let i = 0; i < syncedFrequency.length; i ++) {\n    syncedFrequency[i] = (1/frequency[i]) * (bpm[i] / 60);\n  }\n  return syncedFrequency;\n}\n\nconst useMultipliedLFO = (amplitude, waveform, frequency, phase, trigger) => {\n  return multiply(\n    amplitude,\n    useOscillator(waveform)(frequency, phase, trigger),\n    useMonoSignal()\n  );\n}\n\nconst usePortamento = (pitch, portamento, trigger, gate) => {\n  const result = useMonoSignal();\n  if (result[0] === 0) {\n    result.fill(pitch[0]);\n  }\n  const lastGate = useVal(gate[0]);\n  for (let i = 0; i < pitch.length; i ++) {\n    if (trigger[i] > 0.5 && lastGate.current < 0.5) {\n      result.fill(pitch[i], i);\n    } else {\n      const curvedPortamento = Math.pow(portamento[i], .25);\n      result[i] = (\n        (result[i] * curvedPortamento) + (pitch[i] * (1 - curvedPortamento))\n      );\n    }\n    lastGate.current = gate[i];\n  }\n  return result;\n}\n\nconst useOscillatorSection = (\n  focal,\n  retrigger,\n  trigger,\n  pitch,\n  unison,\n  spread,\n  width,\n  wavetable,\n  wtPos,\n  warpMode,\n  warp,\n  phase,\n  phaseRand,\n  transpose,\n  pan,\n  level,\n  feedback\n) => {\n  const output = useStereoSignal();\n\n  output[0].fill(0);\n  output[1].fill(0);\n\n  const randomPhases = [];\n  for (let u = 0; u < unison; u ++) {\n    randomPhases[u] = useMonoSignal();\n    randomPhases[u].fill(randomPhases[u][randomPhases[u].length - 1]);\n  }\n\n  for (let i = 0; i < trigger.length; i ++) {\n    if (trigger[i] > 0.5) {\n      for (let u = 0; u < randomPhases.length; u ++) {\n        randomPhases[u].fill(((Math.random() * 2) - 1) * Math.PI, i);\n      }\n    }\n  }\n\n  for (let u = 0; u < unison; u ++) {\n    const voicePosition = unison === 1\n      ? 0\n      : (((u / (unison - 1)) * 2) - 1);\n    const voicePitchPosition = Math.floor(unison/2) % 2 === 0 ? voicePosition : -voicePosition;\n    const voiceFrequency = useMonoSignal();\n\n    for (let i = 0; i < pitch.length; i ++) voiceFrequency[i] = pitchToHz(pitch[i] + (voicePitchPosition * spread[i]) + transpose[i]);\n\n    const voicePhase = useMonoSignal();\n    for (let i = 0; i < phase.length; i ++) voicePhase[i] = phase[i] + (randomPhases[u][i] * phaseRand[i]);\n\n    const oscillator = useWavetableReader(wavetable, warpMode, feedback);\n    const monoSignal = oscillator(voiceFrequency, wtPos, warp, voicePhase, retrigger ? trigger : useMonoSignal(), focal && u === 0);\n    const leftLevel = useMonoSignal();\n    const rightLevel = useMonoSignal();\n\n    for (let i = 0; i < monoSignal.length; i ++) {\n      const pRange = (1 - Math.abs(pan[i]));\n      const effectivePan = pan[i] + ((voicePosition * width[i]) * pRange);\n      leftLevel[i] = Math.min(1, (-1 * (effectivePan - 1)))\n      rightLevel[i] = Math.min(1, (effectivePan + 1))\n    }\n\n    const leftOutput = useMonoSignal();\n    const rightOutput = useMonoSignal();\n\n    multiply(monoSignal, leftLevel, leftOutput);\n    multiply(monoSignal, rightLevel, rightOutput);\n    multiply(leftOutput, level, leftOutput);\n    multiply(rightOutput, level, rightOutput);\n    add(output[0], leftOutput, output[0]);\n    add(output[1], rightOutput, output[1]);\n  }\n  return output;\n}\n\nconst getUsedModulators = (matrix) => {\n  // envelope1 is hard-coded to control voice amplitude.\n  const usedModulators = {\n    envelope1: true\n  };\n  Object.keys(matrix).forEach((destination) => {\n    Object.keys(matrix[destination]).forEach((k) => {\n      usedModulators[k] = true;\n    });\n  });\n  return usedModulators;\n}\n\nconst Wavetable = (s, g) => {\n  const usedModulators = useReturnedConst(getUsedModulators, s.matrix)\n  for (let c = 0; c < s.output.length; c ++) s.output[c].fill(0);\n\n  const voices = useRoundRobinVoicing(s.input, s.voiceCount, s.retrigger);\n  for (let v = 0; v < voices.length; v ++) {\n    const exit = enterChildScope();\n\n    // Do the once-per-voice things.\n    const voice = voices[v];\n    const focal = s.interfaceVisible && voice.focal;\n    const trigger = voice.trigger;\n    const gate = voice.gate;\n    const portamentoPitch = usePortamento(voice.pitch, s.portamento, trigger, gate);\n\n    const modulators = {};\n\n    modulators.velocity = useVelocityModulator(voice.velocity, s.velocityCurve);\n    modulators.pitch = usePitchModulator(portamentoPitch, s.minPitch, s.maxPitch);\n    modulators.modWheel = s.input.ccs.modulationWheel;\n\n    const mPre1 = useModulationMatrix(\n      s.matrix,\n      modulators,\n      s\n    );\n\n    // envelope1 is always active.\n    modulators.envelope1 = useMultipliedEnvelope(s.env2Curve, mPre1.envAmplitude1, trigger, gate, mPre1.attack1, mPre1.decay1, mPre1.sustain1, mPre1.release1);\n\n    if (voice.alive && allEqual(modulators.envelope1, 0)) {\n      voice.alive = false;\n    }\n\n    if (!voice.alive && !focal) {\n      exit();\n      continue;\n    }\n\n    modulators.envelope2 = usedModulators.envelope2 ? useMultipliedEnvelope(s.env2Curve, mPre1.envAmplitude2, trigger, gate, mPre1.attack2, mPre1.decay2, mPre1.sustain2, mPre1.release2) : useMonoSignal();\n\n    modulators.envelope3 = usedModulators.envelope3 ? useMultipliedEnvelope(s.env3Curve, mPre1.envAmplitude3, trigger, gate, mPre1.attack3, mPre1.decay3, mPre1.sustain3, mPre1.release3) : useMonoSignal();\n\n    modulators.envelope4 = usedModulators.envelope4 ? useMultipliedEnvelope(s.env4Curve, mPre1.envAmplitude4, trigger, gate, mPre1.attack4, mPre1.decay4, mPre1.sustain4, mPre1.release4) : useMonoSignal();\n\n    const mPre2 = useModulationMatrix(\n      s.matrix,\n      modulators,\n      s\n    );\n\n    const frequency1 = s.sync1 === 'Sync' ? useSyncFrequency(mPre2.frequency1, g.bpm) : mPre2.frequency1;\n    const frequency2 = s.sync2 === 'Sync' ? useSyncFrequency(mPre2.frequency2, g.bpm) : mPre2.frequency2;\n    const frequency3 = s.sync3 === 'Sync' ? useSyncFrequency(mPre2.frequency3, g.bpm) : mPre2.frequency3;\n\n    modulators.lfo1 = usedModulators.lfo1 ? useMultipliedLFO(mPre2.lfoAmplitude1, SUB_WAVEFORMS[s.waveform1], frequency1, mPre2.phase1, trigger, g.bpm, s.sync1) : useMonoSignal();\n    modulators.lfo2 = usedModulators.lfo2 ? useMultipliedLFO(mPre2.lfoAmplitude2, SUB_WAVEFORMS[s.waveform2], frequency2, mPre2.phase2, trigger, g.bpm, s.sync2) : useMonoSignal();\n    modulators.lfo3 = usedModulators.lfo3 ? useMultipliedLFO(mPre2.lfoAmplitude3, SUB_WAVEFORMS[s.waveform3], frequency3, mPre2.phase3, trigger, g.bpm, s.sync3) : useMonoSignal();\n\n    const m = useModulationMatrix(\n      s.matrix,\n      modulators,\n      s\n    );\n\n    const allOnes = useMonoSignal();\n    allOnes.fill(1);\n\n    const bOutput = s.oscBEnabled ? useOscillatorSection(\n      focal,\n      s.retrigger,\n      trigger,\n      portamentoPitch,\n      s.unisonB,\n      m.spreadB,\n      m.widthB,\n      s.wavetableB,\n      m.wtPosB,\n      WARP_MODES[s.warpModeB],\n      m.warpB,\n      m.phaseB,\n      m.phaseRandB,\n      m.transposeB,\n      m.panB,\n      s.modulationMode === 'Off' ? m.levelB : allOnes,\n      s.waveformB,\n    ) : useStereoSignal();\n\n    const fmPitch = useMonoSignal();\n    const amLevel = useMonoSignal();\n    const bOutputMono = useMonoSignal();\n\n    if (s.modulationMode !== 'Off') {\n      add(bOutput[0], bOutput[1], bOutputMono);\n    }\n\n    if (s.modulationMode === 'FM') {\n      const fmScale = useMonoSignal();\n      fmScale.fill(60);\n      const fmModulator = useMonoSignal();\n      multiply(fmScale, m.modulationAmount, fmScale);\n      multiply(bOutputMono, fmScale, fmModulator);\n      add(portamentoPitch, fmModulator, fmPitch);\n    } else {\n      fmPitch.set(portamentoPitch);\n    }\n\n    if (s.modulationMode === 'AM') {\n      const amModulator = useMonoSignal();\n      const offsetModulator = useMonoSignal();\n      const offset = useMonoSignal();\n      offset.fill(1);\n      const multiplier = useMonoSignal();\n      multiplier.fill(0.5);\n      add(bOutputMono, offset, offsetModulator); // 0 - 2\n      multiply(offsetModulator, multiplier, amModulator); // 0 - 1\n      multiply(m.modulationAmount, amModulator, amModulator);\n      add(m.levelA, amModulator, amLevel);\n    } else if (s.modulationMode === 'Ring') {\n      const amModulator = useMonoSignal();\n      multiply(m.modulationAmount, bOutputMono, amModulator);\n      multiply(m.levelA, amModulator, amLevel);\n    } else {\n      amLevel.set(m.levelA);\n    }\n\n    const aOutput = s.oscAEnabled ? useOscillatorSection(\n      focal,\n      s.retrigger,\n      trigger,\n      fmPitch,\n      s.unisonA,\n      m.spreadA,\n      m.widthA,\n      s.wavetableA,\n      m.wtPosA,\n      WARP_MODES[s.warpModeA],\n      m.warpA,\n      m.phaseA,\n      m.phaseRandA,\n      m.transposeA,\n      m.panA,\n      amLevel,\n      s.waveformA,\n    ) : useStereoSignal();\n\n    const subFrequency = useMonoSignal();\n    const totalSubTransposition = add(portamentoPitch, m.transposeSub, useMonoSignal());\n    for (let i = 0; i < subFrequency.length; i ++) subFrequency[i] = pitchToHz(totalSubTransposition[i]);\n\n    const subOscillator = useOscillator(SUB_WAVEFORMS[s.subWaveform], s.subEnabled);\n    const subOutput = subOscillator(subFrequency, useConstantBuffer(0), trigger);\n    const multipliedSubOutput = s.subEnabled ? multiply(subOutput, m.subLevel, useMonoSignal()) : useMonoSignal();\n\n    const leftFilter = useBiQuadFilter();\n    const rightFilter = useBiQuadFilter();\n\n    const preFilterOutput = useStereoSignal();\n    const filterBypassOutput = useStereoSignal();\n\n    preFilterOutput[0].fill(0);\n    preFilterOutput[1].fill(0);\n\n    filterBypassOutput[0].fill(0);\n    filterBypassOutput[1].fill(0);\n\n    if (s.filterA) {\n      add(preFilterOutput[0], aOutput[0], preFilterOutput[0]);\n      add(preFilterOutput[1], aOutput[1], preFilterOutput[1]);\n    } else {\n      add(filterBypassOutput[0], aOutput[0], filterBypassOutput[0]);\n      add(filterBypassOutput[1], aOutput[1], filterBypassOutput[1]);\n    }\n\n    if (s.modulationMode === 'Off') {\n      if (s.filterB) {\n        add(preFilterOutput[0], bOutput[0], preFilterOutput[0]);\n        add(preFilterOutput[1], bOutput[1], preFilterOutput[1]);\n      } else {\n        add(filterBypassOutput[0], bOutput[0], filterBypassOutput[0]);\n        add(filterBypassOutput[1], bOutput[1], filterBypassOutput[1]);\n      }\n    }\n\n    if (s.filterSub) {\n      add(preFilterOutput[0], multipliedSubOutput, preFilterOutput[0]);\n      add(preFilterOutput[1], multipliedSubOutput, preFilterOutput[1]);\n    } else {\n      add(filterBypassOutput[0], multipliedSubOutput, filterBypassOutput[0]);\n      add(filterBypassOutput[1], multipliedSubOutput, filterBypassOutput[1]);\n    }\n\n    const filterOutput = useStereoSignal();\n\n    filterOutput[0].set(leftFilter(\n      preFilterOutput[0],\n      FILTER_TYPES[s.filterType],\n      m.filterCutoff,\n      m.filterResonance,\n      useConstantBuffer(0)\n    ));\n\n    filterOutput[1].set(rightFilter(\n      preFilterOutput[1],\n      FILTER_TYPES[s.filterType],\n      m.filterCutoff,\n      m.filterResonance,\n      useConstantBuffer(0)\n    ));\n\n    const voiceOutput = useStereoSignal();\n\n    const totalAmplitude = multiply(m.master, modulators.envelope1, useMonoSignal());\n\n    add(filterOutput[0], filterBypassOutput[0], voiceOutput[0]);\n    add(filterOutput[1], filterBypassOutput[1], voiceOutput[1]);\n    multiply(voiceOutput[0], totalAmplitude, voiceOutput[0]);\n    multiply(voiceOutput[1], totalAmplitude, voiceOutput[1]);\n    add(s.output[0], voiceOutput[0], s.output[0]);\n    add(s.output[1], voiceOutput[1], s.output[1]);\n\n    const oscIndex = useVal(0);\n    const lastSubOutput = useVal(subOutput[0]);\n    if (focal) {\n      s.modulatedCutoff[0] = m.filterCutoff[0];\n      s.modulatedResonance[0] = m.filterResonance[0];\n      for (let i = 0; i < voiceOutput[0].length; i ++) {\n        if (i % 2 !== 0) continue;\n        if (oscIndex.current < s.oscilloscope.length) {\n          s.oscilloscope[oscIndex.current] = voiceOutput[0][i] + voiceOutput[1][i];\n          oscIndex.current ++;\n        } else if (subOutput[i] > 0 && lastSubOutput.current < 0) {\n          oscIndex.current = 0;\n        }\n        lastSubOutput.current = subOutput[i];\n      }\n    }\n    exit();\n  }\n}\n\nconst WaveformRenderer = (ctx, settings) => {\n  const w = ctx.canvas.width;\n  const h = ctx.canvas.height;\n\n  ctx.clearRect(0, 0, w, h);\n  if (!settings.enabled) {\n    ctx.fillStyle = gray400;\n    ctx.fillRect(0, 0, w, h);\n    ctx.fillStyle = 'rgba(0,0,0,0.2)';\n    ctx.fillRect(0, h/2, w, h/2);\n\n    ctx.fillStyle = gray200;\n    ctx.font = `${h/5}px Arial`;\n    ctx.fillText(settings.text, h/8, h/4);\n    return;\n  }\n  ctx.strokeStyle = 'white';\n  ctx.lineWidth = 1;\n  ctx.beginPath();\n  ctx.moveTo(-w, h/2);\n  for (let i = 0; i < settings.waveform.length; i ++) {\n    ctx.lineTo(\n      (i/(settings.waveform.length - 1)) * w,\n      (h/2) - (settings.waveform[i] * h/2)\n    );\n  }\n  ctx.lineTo(2 * w, h/2);\n  ctx.stroke();\n  ctx.closePath();\n\n  ctx.save();\n  ctx.clip();\n  ctx.fillStyle = 'rgba(255,255,255,0.1)';\n  ctx.fillRect(0, 0, w, h/2);\n  ctx.fillStyle = 'rgba(0,0,0,0.1)';\n  ctx.fillRect(0, h/2, w, h/2);\n  ctx.restore();\n\n  ctx.fillStyle = gray200;\n  ctx.font = `${h/5}px Arial`;\n  ctx.fillText(settings.text, h/8, h/4);\n}\n\nconst OscillatorAWaveformRenderer = (ctx, settings) => WaveformRenderer(ctx, { ...settings, waveform: settings.waveformA, enabled: settings.oscAEnabled, text: 'A' });\nconst OscillatorBWaveformRenderer = (ctx, settings) => WaveformRenderer(ctx, { ...settings, waveform: settings.waveformB, enabled: settings.oscBEnabled, text: 'B' });\n\nconst FilterRenderer = (ctx, settings) => {\n  const w = ctx.canvas.width;\n  const h = ctx.canvas.height;\n  const transferFunction = getBiQuadFilterTransferFunction(\n    sampleRate,\n    FILTER_TYPES[settings.filterType],\n    settings.modulatedCutoff,\n    settings.modulatedResonance,\n    0\n  );\n  const range = 36;\n\n  ctx.clearRect(0, 0, w, h);\n  ctx.save();\n  roundedRect(ctx, 0, 0, w, h, 4);\n  ctx.clip();\n  ctx.beginPath();\n  ctx.moveTo(-w, h);\n  ctx.lineTo(-w, h/2);\n  ctx.strokeStyle = 'white';\n\n  for (let i = 0; i < transferFunction.length; i ++) {\n    const gain = transferFunction[i][1]\n    ctx.lineTo(\n      (i / (transferFunction.length - 1)) * w,\n      h - (((gain + range) / (2 * range)) * h)\n    )\n  }\n  ctx.lineTo(w * 2, h);\n  ctx.stroke();\n  ctx.closePath();\n\n  ctx.save();\n  ctx.clip();\n  ctx.fillStyle = 'rgba(255,255,255,0.1)';\n  ctx.fillRect(0, 0, w, h/2);\n  ctx.restore();\n\n  ctx.restore();\n}\n\nconst Oscilloscope = (ctx, settings) => {\n  const w = ctx.canvas.width;\n  const h = ctx.canvas.height;\n\n  ctx.clearRect(0, 0, w, h);\n\n  ctx.strokeStyle = '#fff';\n  ctx.beginPath();\n  ctx.moveTo(-w, h/2);\n  for (let i = 0; i < settings.oscilloscope.length; i ++) {\n    ctx.lineTo((i/settings.oscilloscope.length) * w, h/2 + (settings.oscilloscope[i] * h/4));\n  }\n  ctx.lineTo(2 * w, h/2);\n  ctx.stroke();\n  ctx.closePath();\n}\n\nconst oscillatorLayout = (suffix, renderFunction) => box(\n  col([\n    box(\n      canvas(`waveform${suffix}`, renderFunction, 290, 100),\n      'auto',\n      'auto',\n      `${blue500}aa`\n    ),\n    spacer(8),\n    row([\n      box(row([\n        label('Enabled'),\n        spacer(5),\n        box(checkbox(`osc${suffix}Enabled`), 24, 24, gray100, 8),\n      ]), 90, 20),\n      spacer(5),\n      box(\n        wavetablePicker(`wavetable${suffix}`),\n        90,\n        20,\n        blue500,\n        4\n      ),\n      spacer(5),\n      box(selector(`warpMode${suffix}`), 90, 20),\n    ]),\n    spacer(10),\n    row([\n      box(row([\n        box(label('Unison'), 42, 20),\n        box(number(`unison${suffix}`), 43, 20),\n      ]), 90, 20, gray100, 4),\n      spacer(5),\n      box(linear(`wtPos${suffix}`), 90, 20, gray300, 4),\n      spacer(5),\n      box(linear(`warp${suffix}`), 90, 20, gray300, 4),\n    ]),\n    spacer(8),\n    row([\n      spacer(5),\n      col([\n        spacer(15),\n        knob(`phase${suffix}`, { size: 25 })\n      ]),\n      col([\n        knob(`phaseRand${suffix}`, { size: 25 }),\n        spacer(15),\n      ]),\n      spacer(5),\n      col([\n        spacer(15),\n        knob(`spread${suffix}`, { size: 25 })\n      ]),\n      col([\n        knob(`width${suffix}`, { size: 25 }),\n        spacer(15),\n      ]),\n      spacer(5),\n      box(knob(`transpose${suffix}`), 56),\n      spacer(5),\n      box(knob(`pan${suffix}`), 54),\n      box(knob(`level${suffix}`), 54),\n    ]),\n    spacer(5),\n    row([\n      box(\n        label('Θ/Rand'),\n        54,\n        20,\n        gray100,\n        4\n      ),\n      spacer(5),\n      box(\n        label('Det/Wid'),\n        54,\n        20,\n        gray100,\n        4\n      ),\n      spacer(5),\n      box(\n        box(label('Transp'), 56),\n        56,\n        20,\n        gray100,\n        4\n      ),\n      spacer(5),\n      box(\n        row([\n          box(label('Pan'), 54),\n          box(label('Level'), 54),\n        ]),\n        54 * 2,\n        20,\n        gray100,\n        4\n      )\n    ]),\n    spacer(5),\n  ]),\n  'auto',\n  'auto',\n  gray200,\n  8\n);\n\nconst filterLayout = () => box(\n  col([\n    box(\n      canvas('filter', FilterRenderer, 122, 50),\n      'auto',\n      'auto',\n      `${blue500}aa`\n    ),\n    spacer(8),\n    box(selector('filterType'), 120, 20),\n    spacer(8),\n    row([\n      label('A'),\n      spacer(3),\n      box(checkbox('filterA'), 24, 24, gray100, 8),\n      spacer(3),\n      label('B'),\n      spacer(3),\n      box(checkbox('filterB'), 24, 24, gray100, 8),\n      spacer(3),\n      label('S'),\n      spacer(3),\n      box(checkbox('filterSub'), 24, 24, gray100, 8),\n    ]),\n    spacer(6),\n    row([\n      box(knob(`filterCutoff`), 56),\n      box(knob(`filterResonance`), 56),\n    ]),\n    spacer(5),\n    box(\n      row([\n        box(label('Freq'), 56),\n        box(label('Res'), 56)\n      ]),\n      'auto',\n      20,\n      gray100,\n      4\n    ),\n    spacer(5)\n  ]),\n  'auto',\n  'auto',\n  gray200,\n  8\n);\n\nconst modulationLayout = () => box(\n  row([\n    spacer(5),\n    col([\n      spacer(3),\n      box(selector('modulationMode'), 72, 20),\n      spacer(3),\n      box(label('Mod B → A'), 65, 20, gray100, 4),\n      spacer(3),\n    ]),\n    spacer(5),\n    conditional('modulationMode', {\n      'Off': box(spacer(35), 35, 35, gray300, 17.5),\n      _other: knob('modulationAmount', { size: 35 })\n    }),\n    spacer(5),\n  ]),\n  'auto',\n  'auto',\n  gray200,\n  8\n);\n\nconst subLayout = () => box(\n  col([\n    spacer(5),\n    box(row([\n      label('Sub Enabled'),\n      spacer(5),\n      box(checkbox(`subEnabled`), 24, 24, gray100, 8),\n    ]), 'auto', 33),\n    spacer(5),\n    box(selector('subWaveform'), 2 * 56, 20),\n    spacer(5),\n    row([\n      box(knob(`subLevel`), 56),\n      box(knob(`transposeSub`), 56),\n    ]),\n    spacer(5),\n    box(\n      row([\n        box(label('Level'), 56),\n        box(label('Transp'), 56)\n      ]),\n      'auto',\n      20,\n      gray100,\n      4\n    ),\n    spacer(5)\n  ]),\n  155,\n  'auto',\n  gray200,\n  8\n);\n\nconst envelopeLayout = (suffix) => box(\n  col([\n    spacer(5),\n    row([\n      box(selector(`env${suffix}Curve`), 56 * 2, 20),\n    ]),\n    spacer(5),\n    row([\n      box(knob(`attack${suffix}`), 56),\n      box(knob(`decay${suffix}`), 56),\n      box(knob(`sustain${suffix}`), 56),\n      box(knob(`release${suffix}`), 56),\n      box(knob(`envAmplitude${suffix}`), 56),\n    ]),\n    spacer(5),\n    row([\n      box(\n        row([\n          box(label('Attack'), 56),\n          box(label('Decay'), 56),\n          box(label('Sustain'), 56),\n          box(label('Release'), 56),\n          box(label('Level'), 56)\n        ]),\n        'auto',\n        20,\n        gray100,\n        4\n      ),\n    ]),\n    spacer(5),\n  ]),\n  'auto',\n  'auto',\n  gray200,\n  8\n);\n\nconst lfoLayout = (suffix) => box(\n  col([\n    spacer(5),\n    row([\n      box(selector(`sync${suffix}`, { options: [ 'Sync', 'Free' ] }), 56 * 2, 20),\n      box(selector(`waveform${suffix}`), 56 * 2, 20),\n    ]),\n    spacer(5),\n    row([\n      box(\n        conditional(\n          `sync${suffix}`,\n          {\n            Sync: knob(`frequency${suffix}`, { options: LFO_BEAT_FRACTIONS }),\n            Free: knob(`frequency${suffix}`)\n          }\n        ),\n        56\n      ),\n      box(knob(`phase${suffix}`), 56),\n      box(knob(`lfoAmplitude${suffix}`), 56),\n    ]),\n    spacer(5),\n    row([\n      box(\n        row([\n          box(\n            conditional(`sync${suffix}`, { Sync: label('Beats'), Free: label('Freq') }),\n            56\n          ),\n          box(label('Phase'), 56),\n          box(label('Level'), 56),\n        ]),\n        'auto',\n        20,\n        gray100,\n        4\n      ),\n    ]),\n    spacer(5),\n  ]),\n  'auto',\n  'auto',\n);\n\nconst globalsLayout = () => box(\n  row([\n    spacer(5),\n    col([\n      spacer(14),\n      row([\n        box(box(checkbox('retrigger'), 24, 24, gray100, 8), 56),\n        spacer(3),\n        box(number('voiceCount'), 54, 20, gray300, 4),\n      ]),\n      spacer(10),\n      box(\n        row([\n          box(label('Retrig'), 56),\n          box(label('Voices'), 56),\n        ]),\n        'auto',\n        20,\n        gray100,\n        4\n      ),\n      spacer(5),\n    ]),\n    spacer(5),\n    box(\n      canvas(\n        'oscilloscope',\n        Oscilloscope,\n        158,\n        64\n      ),\n      'auto',\n      'auto',\n      gray100,\n      4\n    ),\n    spacer(5),\n    col([\n      spacer(5),\n      row([\n        box(modulationAssigner('modWheel'), 56),\n        spacer(5),\n        box(knob('portamento'), 56),\n        spacer(5),\n        box(row([\n          col([\n            spacer(15),\n            knob('minPitch', { size: 25 })\n          ]),\n          col([\n            knob('maxPitch', { size: 25 }),\n            spacer(15)\n          ]),\n        ]), 56),\n        box(modulationAssigner('pitch'), 56),\n        spacer(5),\n        box(knob('velocityCurve'), 56),\n        box(modulationAssigner('velocity'), 56),\n        spacer(5),\n        box(knob('master'), 56)\n      ]),\n      spacer(4),\n      row([\n        box(label('Mod'), 56, 20, gray100, 4),\n        spacer(5),\n        box(label('Porta'), 56, 20, gray100, 4),\n        spacer(5),\n        box(\n          row([\n            box(label('Range'), 56, 20),\n            box(label('Pitch'), 56, 20),\n          ]),\n          'auto',\n          'auto',\n          gray100,\n          4\n        ),\n        spacer(5),\n        box(\n          row([\n            box(label('V Curve'), 56, 20),\n            box(label('Velocity'), 56, 20),\n          ]),\n          'auto',\n          'auto',\n          gray100,\n          4\n        ),\n        spacer(5),\n        box(label('Master'), 56, 20, gray100, 4),\n      ]),\n      spacer(5),\n    ]),\n    spacer(5),\n  ]),\n  'auto',\n  'auto',\n  gray200,\n  8\n);\nconst lfoTabLayout = (suffix) => {\n  const tabLayout = (color) => box(\n    constantSetter('focusedLFO', `LFO ${suffix}`, col([\n      spacer(4),\n      row([\n        spacer(7),\n        box(label(`LFO${suffix}`), 30, 20),\n        spacer(5),\n        modulationAssigner(`lfo${suffix}`, { size: 30 }),\n        spacer(6)\n      ]),\n      spacer(4),\n    ])),\n    'auto',\n    'auto',\n    color,\n    0\n  );\n  return conditional('focusedLFO', { [`LFO ${suffix}`]: tabLayout(gray200), _other: tabLayout(gray125) })\n}\n\nconst envTabLayout = (suffix) => {\n  const tabLayout = (color) => box(\n    constantSetter('focusedEnvelope', `Env ${suffix}`, col([\n      spacer(4),\n      row([\n        spacer(7),\n        box(label(`Env${suffix}`), 30, 20),\n        spacer(5),\n        modulationAssigner(`envelope${suffix}`, { size: 30 }),\n        spacer(6)\n      ]),\n      spacer(4),\n    ])),\n    'auto',\n    'auto',\n    color,\n    0\n  );\n  return conditional('focusedEnvelope', { [`Env ${suffix}`]: tabLayout(gray200), _other: tabLayout(gray125) })\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: Wavetable,\n  inputs: {\n    input: midi('In'),\n\n    portamento: numberSetting('Portamento', { defaultValue: 0, min: 0, max: 1 }),\n    minPitch: numberSetting('Minimum Pitch', { defaultValue: 0, min: 0, max: 127 }),\n    maxPitch: numberSetting('Maximum Pitch', { defaultValue: 127, min: 0, max: 127 }),\n    velocityCurve: numberSetting('Velocity Curve', { defaultValue: 0, min: -1, max: 1 }),\n\n    spreadA: numberSetting('Oscillator A Spread', { defaultValue: 0, min: 0, max: 2 }),\n    widthA: numberSetting('Oscillator A Width', { defaultValue: 1, min: 0, max: 1 }),\n    wtPosA: numberSetting('Oscillator A WT Pos', { defaultValue: 0, min: 0, max: 1 }),\n    warpA: numberSetting('Oscillator A Warp', { defaultValue: 0, min: -1, max: 1 }),\n    transposeA: numberSetting('Oscillator A Transpose', { defaultValue: 0, min: -12, max: 12 }),\n    phaseA: numberSetting('Oscillator A Base Phase', { defaultValue: 0, min: -Math.PI, max: Math.PI }),\n    phaseRandA: numberSetting('Oscillator A Random Phase', { defaultValue: Math.PI, min: 0, max: Math.PI }),\n    panA: numberSetting('Oscillator A Pan', { defaultValue: 0, max: 1, min: -1 }),\n    levelA: dbSetting('Oscillator A Level', { defaultValue: -6, max: 0 }),\n\n    modulationAmount: numberSetting('Modulation Amount', { defaultValue: 0, max: 1, min: 0 }),\n\n    spreadB: numberSetting('Oscillator B Spread', { defaultValue: 0, min: 0, max: 2 }),\n    widthB: numberSetting('Oscillator B Width', { defaultValue: 1, min: 0, max: 1 }),\n    wtPosB: numberSetting('Oscillator B WT Pos', { defaultValue: 0, min: 0, max: 1 }),\n    warpB: numberSetting('Oscillator B Warp', { defaultValue: 0, min: -1, max: 1 }),\n    transposeB: numberSetting('Oscillator B Transpose', { defaultValue: 0, min: -12, max: 12 }),\n    phaseB: numberSetting('Oscillator B Base Phase', { defaultValue: 0, min: -Math.PI, max: Math.PI }),\n    phaseRandB: numberSetting('Oscillator B Random Phase', { defaultValue: Math.PI, min: 0, max: Math.PI }),\n    panB: numberSetting('Oscillator B Pan', { defaultValue: 0, max: 1, min: -1 }),\n    levelB: dbSetting('Oscillator B Level', { defaultValue: -6, max: 0 }),\n\n    subLevel: dbSetting('Sub Level', { defaultValue: -6, max: 0 }),\n    transposeSub: numberSetting('Sub Transposition', { defaultValue: -12, max: 0, min: -24 }),\n\n    filterCutoff: frequencySetting('Filter Cutoff Frequency', 20000),\n    filterResonance: numberSetting('Filter Resonance', { defaultValue: Math.sqrt(.5), min: .1, max: 18, curve: Curve.Log2 }),\n\n    attack1: adsrTimeSetting('Envelope 1 Attack', 1),\n    decay1: adsrTimeSetting('Envelope 1 Decay', 300),\n    sustain1: zeroOneSetting('Envelope 1 Sustain', 1),\n    release1: adsrTimeSetting('Envelope 1 Release', 100),\n    envAmplitude1: zeroOneSetting('Envelope 1 Level', 1),\n\n    attack2: adsrTimeSetting('Envelope 2 Attack', 1),\n    decay2: adsrTimeSetting('Envelope 2 Decay', 300),\n    sustain2: zeroOneSetting('Envelope 2 Sustain', 0),\n    release2: adsrTimeSetting('Envelope 2 Release', 100),\n    envAmplitude2: zeroOneSetting('Envelope 2 Level', 1),\n\n    attack3: adsrTimeSetting('Envelope 3 Attack', 1),\n    decay3: adsrTimeSetting('Envelope 3 Decay', 300),\n    sustain3: zeroOneSetting('Envelope 3 Sustain', 0),\n    release3: adsrTimeSetting('Envelope 3 Release', 100),\n    envAmplitude3: zeroOneSetting('Envelope 3 Level', 1),\n\n    attack4: adsrTimeSetting('Envelope 4 Attack', 1),\n    decay4: adsrTimeSetting('Envelope 4 Decay', 300),\n    sustain4: zeroOneSetting('Envelope 4 Sustain', 0),\n    release4: adsrTimeSetting('Envelope 4 Release', 100),\n    envAmplitude4: zeroOneSetting('Envelope 4 Level', 1),\n\n    frequency1: numberSetting('LFO 1 Frequency', { defaultValue: 1, min: 0.001, max: 100, curve: Curve.Log2 }),\n    phase1: numberSetting('Phase 1', { defaultValue: 0, min: -Math.PI, max: Math.PI }),\n    lfoAmplitude1: zeroOneSetting('LFO Level 1', 0.5),\n\n    frequency2: numberSetting('LFO 2 Frequency', { defaultValue: 1, min: 0.001, max: 100, curve: Curve.Log2 }),\n    phase2: numberSetting('Phase 2', { defaultValue: 0, min: -Math.PI, max: Math.PI }),\n    lfoAmplitude2: zeroOneSetting('LFO Level 2', 0.5),\n\n    frequency3: numberSetting('LFO 3 Frequency', { defaultValue: 1, min: 0.001, max: 100, curve: Curve.Log2 }),\n    phase3: numberSetting('Phase 3', { defaultValue: 0, min: -Math.PI, max: Math.PI }),\n    lfoAmplitude3: zeroOneSetting('LFO Level 3', 0.5),\n\n    master: dbSetting('Master', { defaultValue: -6, max: 0 }),\n  },\n\n  modulators: {\n    envelope1: modulator('Envelope 1', 0, 1),\n    envelope2: modulator('Envelope 2', 0, 1),\n    envelope3: modulator('Envelope 3', 0, 1),\n    envelope4: modulator('Envelope 4', 0, 1),\n\n    lfo1: modulator('LFO 1', -1, 1),\n    lfo2: modulator('LFO 2', -1, 1),\n    lfo3: modulator('LFO 3', -1, 1),\n\n    pitch: modulator('Pitch', 0, 127),\n    velocity: modulator('Velocity', 0, 1),\n    modWheel: modulator('Mod Wheel', 0, 1),\n  },\n\n  constants: {\n    oscAEnabled: booleanConstant('Oscillator A Enabled', true),\n    oscBEnabled: booleanConstant('Oscillator B Enabled', false),\n    subEnabled: booleanConstant('Sub Enabled', false),\n\n    warpModeA: enumConstant('Oscillator A Warp Mode', Object.keys(WARP_MODES)),\n    warpModeB: enumConstant('Oscillator B Warp Mode', Object.keys(WARP_MODES)),\n    modulationMode: enumConstant('Modulation Mode', MODULATION_MODES),\n\n    wavetableA: wavetableConstant('Oscillator A Wavetable'),\n    wavetableB: wavetableConstant('Oscillator B Wavetable'),\n\n    unisonA: numberConstant('Oscillator A Unison Count', { defaultValue: 1, min: 1, max: 8 }),\n    unisonB: numberConstant('Oscillator B Unison Count', { defaultValue: 1, min: 1, max: 8 }),\n\n    filterType: enumConstant('Filter Type', Object.keys(FILTER_TYPES)),\n    subWaveform: enumConstant('Sub Waveform', Object.keys(SUB_WAVEFORMS)),\n\n    filterA: booleanConstant('Oscillator A to Filter', true),\n    filterB: booleanConstant('Oscillator B to Filter', true),\n    filterSub: booleanConstant('Sub to Filter', true),\n\n    focusedLFO: enumConstant('Focused LFO', ['LFO 1', 'LFO 2', 'LFO 3'], 'LFO 1'),\n    focusedEnvelope: enumConstant('Focused Envelope', ['Env 1', 'Env 2', 'Env 3', 'Env 4'], 'Env 1'),\n\n    env1Curve: enumConstant('Envelope 1 Curve', ['Linear', 'Exponential']),\n    env2Curve: enumConstant('Envelope 2 Curve', ['Linear', 'Exponential']),\n    env3Curve: enumConstant('Envelope 3 Curve', ['Linear', 'Exponential']),\n    env4Curve: enumConstant('Envelope 4 Curve', ['Linear', 'Exponential']),\n\n    waveform1: enumConstant('LFO 1 Waveform', Object.keys(SUB_WAVEFORMS), 'Sine'),\n    waveform2: enumConstant('LFO 2 Waveform', Object.keys(SUB_WAVEFORMS), 'Sine'),\n    waveform3: enumConstant('LFO 3 Waveform', Object.keys(SUB_WAVEFORMS), 'Sine'),\n\n    sync1: enumConstant('LFO 1 Tempo Sync', ['Sync', 'Free']),\n    sync2: enumConstant('LFO 2 Tempo Sync', ['Sync', 'Free']),\n    sync3: enumConstant('LFO 3 Tempo Sync', ['Sync', 'Free']),\n\n    retrigger: booleanConstant('Retrigger', true),\n    voiceCount: numberConstant('Voice Count', { defaultValue: 4, min: 1, max: 16 })\n  },\n\n  outputs: {\n    output: stereo('Out')\n  },\n\n  sharedArrays: {\n    waveformA: 1024,\n    waveformB: 1024,\n    modulatedCutoff: 1,\n    modulatedResonance: 1,\n    oscilloscope: 512,\n  },\n\n  presetTags: {\n    'Instrument': [\n      'Bass',\n      'Synth Lead',\n      'Synth Pad',\n      'Piano/Keys',\n      'Percussion',\n      'Plucked Strings',\n      'Bowed Strings',\n      'Brass',\n      'Woodwinds',\n      'Sound Effect',\n    ],\n    'Genre': [\n      'Ambient',\n      'Chill',\n      'Dubstep',\n      'EDM',\n      'Funk',\n      'Hip Hop',\n      'House',\n      'Jazz',\n      'Lo-Fi',\n      'Soundtrack',\n      'Metal',\n      'Pop',\n      'R&B',\n      'Rock',\n      'Techno',\n      'Trance',\n    ],\n    'Playback': [\n      'Monophonic',\n      'Polyphonic',\n      'Portamento',\n      'Arpeggiated',\n      'Long/Evolving',\n      'Chord',\n      'Slow Attack',\n      'Long Release'\n    ]\n  },\n\n  layout: col([\n    spacer(5),\n    row([\n      spacer(5),\n      oscillatorLayout('A', OscillatorAWaveformRenderer),\n      spacer(1),\n      col([\n        modulationLayout(),\n        spacer(1),\n        filterLayout(),\n      ]),\n      spacer(1),\n      oscillatorLayout('B', OscillatorBWaveformRenderer),\n      spacer(5),\n    ]),\n    spacer(1),\n    row([\n      spacer(5),\n      box(\n        col([\n          row([\n            lfoTabLayout('1'),\n            lfoTabLayout('2'),\n            lfoTabLayout('3'),\n          ]),\n          conditional('focusedLFO', {\n            'LFO 1': lfoLayout('1'),\n            'LFO 2': lfoLayout('2'),\n            'LFO 3': lfoLayout('3'),\n          }),\n        ]),\n        'auto',\n        'auto',\n        gray200,\n        8\n      ),\n      spacer(1),\n      subLayout(),\n      spacer(1),\n      box(\n        col([\n          row([\n            envTabLayout('1'),\n            envTabLayout('2'),\n            envTabLayout('3'),\n            envTabLayout('4'),\n          ]),\n          conditional('focusedEnvelope', {\n            'Env 1': envelopeLayout('1'),\n            'Env 2': envelopeLayout('2'),\n            'Env 3': envelopeLayout('3'),\n            'Env 4': envelopeLayout('4'),\n          }),\n        ]),\n        'auto',\n        'auto',\n        gray200,\n        8\n      ),\n      spacer(5),\n    ]),\n    spacer(1),\n    globalsLayout(),\n    spacer(5)\n  ])\n});\n	\N	2023-03-24 13:10:59.476638	df142a04-31c6-4495-a8a4-c49f8429d557
3	Avg to Mono	1	const AvgToMonoEffect = ({ input, output }) => {\n  for (let i = 0; i < output.length; i ++) {\n    output[i] = (input[0][i] + input[1][i]) / 2;\n  }\n};\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: AvgToMonoEffect,\n  inputs: {\n    input: stereo('In'),\n  },\n  outputs: {\n    output: mono('Out'),\n  }\n});\n	\N	2023-03-24 15:29:28.055775	0198a6e5-af8b-44b2-b51b-0ac7591dfe76
4	MIDI-CV	1	const MIDIToCV = ({ input, gate, frequency, trigger }) => {\n  const voices = useRoundRobinVoicing(input, 1);\n  gate.set(voices[0].gate);\n  trigger.set(voices[0].trigger);\n  frequency.set(voices[0].frequency);\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: MIDIToCV,\n  inputs: {\n    input: midi('In')\n  },\n  outputs: {\n    frequency: expose(frequencySetting('Frequency', 1000)),\n    trigger: expose(zeroOneSetting('Trigger', 0)),\n    gate: expose(zeroOneSetting('Gate', 0)),\n  }\n});\n\n	\N	2023-03-24 15:30:05.966422	607a1183-6853-4972-b600-a8d919e2bb80
5	Math	1	const Effect = ({ input, output, min, max, curve }) => {\n  for (let i = 0; i < input.length; i++) {\n    output[i] = min[i] + (Math.pow(input[i], curve[i]) * (max[i] - min[i]));\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Effect,\n  inputs: {\n    input: expose(zeroOneSetting('In')),\n    curve: numberSetting('Curve', { defaultValue: 1, min: .5, max: 2, curve: Curve.Log2 }),\n    min: zeroOneSetting('Min', 0),\n    max: zeroOneSetting('Max', 1),\n  },\n  outputs: {\n    output: expose(zeroOneSetting('Out')),\n  },\n  layout: row([\n    titled.knob('min'),\n    titled.knob('max'),\n    titled.knob('curve'),\n  ])\n});\n	\N	2023-03-24 15:31:00.701938	689d5a16-8812-4b98-989a-1444069cded3
7	LFO	1	const LFO = ({ frequency, trigger, saw, sin, square, triangle, phase, noise }) => {\n  const sawOsc = useOscillator(Waveform.Sawtooth);\n  const sinOsc = useOscillator(Waveform.Sine);\n  const squareOsc = useOscillator(Waveform.Square);\n  const triangleOsc = useOscillator(Waveform.Triangle);\n  const noiseOsc = useOscillator(Waveform.Noise);\n  saw.set(sawOsc(frequency, phase, trigger));\n  sin.set(sinOsc(frequency, phase, trigger));\n  square.set(squareOsc(frequency, phase, trigger));\n  triangle.set(triangleOsc(frequency, phase, trigger));\n  noise.set(noiseOsc(frequency));\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: LFO,\n  inputs: {\n    frequency: expose(numberSetting('Frequency', { defaultValue: 1, min: 0.001, max: 100, curve: Curve.Log2, unit: InterfaceUnit.Hz })),\n    trigger: expose(zeroOneSetting('Trigger', 0)),\n    phase: expose(numberSetting('Phase', { defaultValue: 0, min: -Math.PI, max: Math.PI }))\n  },\n  outputs: {\n    saw: mono('Saw'),\n    sin: mono('Sine'),\n    square: mono('Square'),\n    triangle: mono('Triangle'),\n    noise: mono('Noise')\n  }\n});\n	\N	2023-03-24 15:32:22.192783	fa07f278-0421-4aae-bdf6-bfddcb78033b
8	Mono to Stereo	1	const MonoToStereoEffect = ({ input, output }) => {\n  for (let i = 0; i < input.length; i ++) {\n    output[0][i] = input[i];\n    output[1][i] = input[i];\n  }\n};\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: MonoToStereoEffect,\n  inputs: {\n    input: mono('In'),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	\N	2023-03-24 15:33:53.833444	03c9e5e9-e35a-4b0e-8c25-6873cd133761
9	Envelope Generator	1	const Envelope = ({ trigger, gate, output, attack, decay, sustain, release }) => {\n  const processEnvelope = useEnvelope();\n  output.set(processEnvelope(\n    trigger,\n    gate,\n    attack,\n    decay,\n    sustain,\n    release,\n  ));\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Envelope,\n  inputs: {\n    trigger: expose(zeroOneSetting('Trigger')),\n    gate: expose(zeroOneSetting('Gate')),\n    attack: adsrTimeSetting('Attack', 1),\n    decay: adsrTimeSetting('Decay', 100),\n    sustain: zeroOneSetting('Sustain', 0.5),\n    release: adsrTimeSetting('Release', 300),\n  },\n  outputs: {\n    output: { ...mono('Out'), min: 0 },\n  }\n});\n	\N	2023-03-24 15:34:39.194061	e6b44871-315e-4f6c-a1e6-d1718d327179
10	Envelope Follower	1	const gate = new Float64Array(bufferSize);\n\nconst EnvelopeFollower = ({ input, output, attack, decay, sustain, release, threshold, hold }) => {\n  const processEnvelope = useEnvelope();\n  const lastGate = useVal(0);\n  const trigger = useMonoSignal(input.length);\n  trigger.fill(0);\n  for (let i = 0; i < input.length; i++) {\n    const absInputSample = Math.abs(input[i]);\n    if (absInputSample > threshold[i]) {\n      gate[i] = 1;\n      if (lastGate.current !== 1) {\n        trigger[i] = 1;\n      }\n    } else if (absInputSample < threshold[i] * (1 - hold[i])) {\n      gate[i] = 0;\n    } else {\n      gate[i] = lastGate.current;\n    }\n    lastGate.current = gate[i];\n  }\n  output.set(\n    processEnvelope(\n      trigger,\n      gate,\n      attack,\n      decay,\n      sustain,\n      release,\n    )\n  );\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: EnvelopeFollower,\n  inputs: {\n    input: stereo('In'),\n    threshold: zeroOneSetting('Trigger', 0.3),\n    hold: zeroOneSetting('Hold', 0.3),\n    attack: adsrTimeSetting('Attack', 1),\n    decay: adsrTimeSetting('Decay', 100),\n    sustain: zeroOneSetting('Sustain', 0.5),\n    release: adsrTimeSetting('Release', 50),\n  },\n  outputs: {\n    output: { ...mono('Out'), min: 0 },\n  }\n});\n	\N	2023-03-24 15:38:05.452958	0c01edf7-9391-4122-bfce-2224e6a97862
11	Tempo Delay	1	const TempoDelayEffect = ({ input, output, mix, feedback, beats }, { bpm }) => {\n  const delay = useDelay();\n  const timeSamples = useMonoSignal();\n  for (let i = 0; i < beats.length; i++) {\n    timeSamples[i] = beats[i] * (60 / bpm[i]) * sampleRate;\n  }\n  const delayed = delay(input, timeSamples, feedback);\n  for (let i = 0; i < input.length; i ++) {\n    output[i] = ((1 - mix[i]) * input[i]) + (mix[i] * delayed[i]);\n  }\n};\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: TempoDelayEffect,\n  inputs: {\n    input: stereo('In'),\n    mix: numberSetting('Mix', { defaultValue: 0.3, min: 0, max: 1 }),\n    feedback: numberSetting('Feedback', { defaultValue: 0.25, min: 0, max: 1 }),\n    beats: numberSetting('Beats', { defaultValue: 0.75, min: 0, max: 4 }),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	\N	2023-03-24 15:38:24.121712	933cb96b-27e4-498f-8331-f155dce0050b
12	Time Delay	1	const sampleRateMultiplier = sampleRate / 1000;\n\nconst TimeDelayEffect = ({ input, output, mix, feedback, milliseconds }) => {\n  const delay = useDelay();\n  const timeSamples = useMonoSignal();\n  for (let i = 0; i < milliseconds.length; i++) {\n    timeSamples[i] = milliseconds[i] * (sampleRateMultiplier);\n  }\n  const delayed = delay(input, timeSamples, feedback);\n  for (let i = 0; i < input.length; i ++) {\n    output[i] = ((1 - mix[i]) * input[i]) + (mix[i] * delayed[i]);\n  }\n};\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: TimeDelayEffect,\n  inputs: {\n    input: stereo('In'),\n    mix: numberSetting('Mix', { defaultValue: 0.3, min: 0, max: 1 }),\n    feedback: numberSetting('Feedback', { defaultValue: 0.25, min: 0, max: 1 }),\n    milliseconds: numberSetting('Milliseconds', { defaultValue: 150, min: 0, max: 5000 }),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	\N	2023-03-24 15:38:42.220044	5ce0acae-ffdf-4604-9692-8e3ab25b2eeb
2	Drum & Drummer	1	const DrumVoice = ({\n  wavetable,\n  trigger,\n  gate,\n  velocity,\n  output,\n  basePitch,\n  modPitch,\n  wtTime,\n  pitchTime,\n  gainTime,\n  filterTime\n}) => {\n  const filterEnvelope = useEnvelope(Curve.Log2, false);\n  const gainEnvelope = useEnvelope(Curve.Log2, false);\n  const pitchEnvelope = useEnvelope(Curve.Log2, false);\n  const wtPosEnvelope = useEnvelope(Curve.Linear, false);\n\n  const oscillator = useWavetableReader(wavetable, WavetableWarpMode.Off);\n\n  const wtPos = wtPosEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(0),\n    wtTime,\n    useConstantBuffer(0),\n    wtTime,\n  );\n\n  subtract(useConstantBuffer(1), wtPos, wtPos);\n\n  const pitch = pitchEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(0),\n    pitchTime,\n    useConstantBuffer(0),\n    pitchTime,\n  );\n\n  const frequency = useMonoSignal();\n  for (let i = 0; i < pitch.length; i ++) {\n    frequency[i] = pitchToHz((pitch[i] * modPitch[i]) + basePitch[i]);\n  }\n  const gain = gainEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(1 * MS_TO_SAMPLES),\n    gainTime,\n    useConstantBuffer(0),\n    gainTime\n  );\n\n  const oscillatorOutput = oscillator(frequency, wtPos, useConstantBuffer(0), useConstantBuffer(0), trigger);\n\n  const filterFrequency = filterEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(0),\n    filterTime,\n    useConstantBuffer(0),\n    filterTime\n  );\n\n  for (let i = 0; i < filterFrequency.length; i ++) {\n    filterFrequency[i] = zeroOneToHz((filterFrequency[i] * 0.75) + 0.25);\n  }\n\n  const voiceOutput = useMonoSignal();\n\n  const filter = useBiQuadFilter();\n\n  const filterOutput = filter(oscillatorOutput, BiQuadFilterMode.LowPass, filterFrequency, useConstantBuffer(0.2), useConstantBuffer(0));\n\n  multiply(filterOutput, gain, voiceOutput);\n\n  add(output[0], voiceOutput, output[0]);\n  add(output[1], voiceOutput, output[1]);\n}\n\nconst RattleVoice = (trigger, gate, velocity, output, attackTime, decayTime) => {\n  const gainEnvelope = useEnvelope(Curve.Log2, false);\n\n  const gain = gainEnvelope(\n    trigger,\n    gate,\n    attackTime,\n    decayTime,\n    useConstantBuffer(0),\n    decayTime\n  );\n\n  multiply(gain, gain, gain);\n  multiply(gain, useConstantBuffer(0.35), gain);\n\n  const noise = useOscillator(Waveform.Noise);\n  const voiceOutput = useMonoSignal();\n  const filter = useBiQuadFilter();\n  const shaper = useBiQuadFilter();\n  const filtered = filter(noise(useConstantBuffer(8000)), BiQuadFilterMode.BandPass, useConstantBuffer(3000), useConstantBuffer(.25), useConstantBuffer(0));\n  const shaped = shaper(filtered, BiQuadFilterMode.EQBand, useConstantBuffer(4000), useConstantBuffer(.5), useConstantBuffer(1.5));\n  multiply(shaped, gain, voiceOutput);\n\n  add(output[0], voiceOutput, output[0]);\n  add(output[1], voiceOutput, output[1]);\n}\n\nconst hatToneFrequencies = [953, 950, 1023, 1135, 1258].map((x) => x * 1.3);\n\nconst CymbalVoice = (settings) => {\n  const { wavetable, trigger, gate, velocity, output, attackTime, decayTime } = settings;\n  const zero = useConstantBuffer(0);\n  const gainEnvelope = useEnvelope(Curve.Log2, false);\n  const carrierWtPosEnvelope = useEnvelope(Curve.Linear, false);\n  const modulatorWtPosEnvelope = useEnvelope(Curve.Linear, false);\n\n  const gain = gainEnvelope(\n    trigger,\n    gate,\n    attackTime,\n    decayTime,\n    useConstantBuffer(0),\n    decayTime,\n  );\n  multiply(gain, gain, gain);\n  multiply(gain, useConstantBuffer(0.4), gain);\n\n  const carrierWtPos = carrierWtPosEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(0),\n    useConstantBuffer(decayTime),\n    useConstantBuffer(0),\n    useConstantBuffer(decayTime),\n  );\n  subtract(useConstantBuffer(1), carrierWtPos, carrierWtPos);\n\n  const modulatorWtPos = modulatorWtPosEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(0),\n    useConstantBuffer(decayTime),\n    useConstantBuffer(0),\n    useConstantBuffer(decayTime),\n  )\n  subtract(useConstantBuffer(1), modulatorWtPos, modulatorWtPos);\n\n  const summedTones = useMonoSignal();\n  summedTones.fill(0);\n\n  const osc1 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const osc2 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const osc3 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const osc4 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const osc5 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n\n  const osc1Freq = settings.carrierRoot;\n\n  const osc2Freq = useMonoSignal();\n  const scaledDelta1 = useMonoSignal();\n  multiply(settings.carrier1Delta, settings.carrierScale, scaledDelta1);\n  add(osc1Freq, scaledDelta1, osc2Freq);\n\n  const osc3Freq = useMonoSignal();\n  const scaledDelta2 = useMonoSignal();\n  multiply(settings.carrier2Delta, settings.carrierScale, scaledDelta2);\n  add(osc2Freq, scaledDelta2, osc3Freq);\n\n  const osc4Freq = useMonoSignal();\n  const scaledDelta3 = useMonoSignal();\n  multiply(settings.carrier3Delta, settings.carrierScale, scaledDelta3);\n  add(osc3Freq, scaledDelta3, osc4Freq);\n\n  const osc5Freq = useMonoSignal();\n  const scaledDelta4 = useMonoSignal();\n  multiply(settings.carrier4Delta, settings.carrierScale, scaledDelta4);\n  add(osc4Freq, scaledDelta4, osc5Freq);\n\n  add(summedTones, osc1(osc1Freq, carrierWtPos, zero, zero, trigger), summedTones);\n  add(summedTones, osc2(osc2Freq, carrierWtPos, zero, zero, trigger), summedTones);\n  add(summedTones, osc3(osc3Freq, carrierWtPos, zero, zero, trigger), summedTones);\n  add(summedTones, osc4(osc4Freq, carrierWtPos, zero, zero, trigger), summedTones);\n  add(summedTones, osc5(osc5Freq, carrierWtPos, zero, zero, trigger), summedTones);\n\n  const ringOsc1 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const ringOsc2 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const ring1 = ringOsc1(settings.modulator1, modulatorWtPos, useConstantBuffer(0), useConstantBuffer(0), trigger);\n  const ring2 = ringOsc2(settings.modulator2, modulatorWtPos, useConstantBuffer(0), useConstantBuffer(0), trigger);\n\n  const bandpass1 = useBiQuadFilter();\n  const bandpass2 = useBiQuadFilter();\n  const highpass = useBiQuadFilter();\n\n  const bandpassed1 = bandpass1(summedTones, BiQuadFilterMode.BandPass, settings.band1Freq, settings.band1Res, useConstantBuffer(0));\n  const bandpassed2 = bandpass2(summedTones, BiQuadFilterMode.BandPass, settings.band2Freq, settings.band2Res, useConstantBuffer(0));\n\n  multiply(ring1, bandpassed1, ring1);\n  multiply(ring2, bandpassed2, ring2);\n  add(ring1, ring2, summedTones);\n\n  highpassed = highpass(summedTones, BiQuadFilterMode.HighPass, useConstantBuffer(1000), useConstantBuffer(1), useConstantBuffer(0));\n  multiply(highpassed, gain, highpassed);\n  add(output[0], highpassed, output[0]);\n  add(output[1], highpassed, output[1]);\n}\n\nconst hatPitchTiming = {\n  54: {\n    cymbalDecay: 200 * MS_TO_SAMPLES,\n    cymbalAttack: 0,\n    noiseDecay: 100 * MS_TO_SAMPLES,\n    noiseAttack: 0 * MS_TO_SAMPLES,\n    noiseGain: 1,\n    cymbalGain: 1,\n  },\n  56: {\n    cymbalDecay: 100 * MS_TO_SAMPLES,\n    cymbalAttack: 4 * MS_TO_SAMPLES,\n    noiseDecay: 25 * MS_TO_SAMPLES,\n    noiseAttack: 4 * MS_TO_SAMPLES,\n    noiseGain: 0.25,\n    cymbalGain: 0.5,\n  },\n  58: {\n    cymbalDecay: 1600 * MS_TO_SAMPLES,\n    cymbalAttack: 0,\n    noiseDecay: 150 * MS_TO_SAMPLES,\n    noiseAttack: 0 * MS_TO_SAMPLES,\n    noiseGain: 1,\n    cymbalGain: 1,\n  },\n};\n\nconst DigitalDrums = (settings) => {\n  const { input, output, kick, snare, hat, hiTom, lowTom, midTom, flrTom, kickWavetable, snareWavetable, hatWavetable, tomWavetable } = settings;\n  output[0].fill(0);\n  output[1].fill(0);\n\n  kick[0].fill(0);\n  kick[1].fill(0);\n\n  snare[0].fill(0);\n  snare[1].fill(0);\n\n  hat[0].fill(0);\n  hat[1].fill(0);\n\n  hiTom[0].fill(0);\n  hiTom[1].fill(0);\n\n  lowTom[0].fill(0);\n  lowTom[1].fill(0);\n\n  midTom[0].fill(0);\n  midTom[1].fill(0);\n\n  flrTom[0].fill(0);\n  flrTom[1].fill(0);\n\n\n\n  const kickVoice = useSingleVoice(input, [48], 2);\n  const snareVoice = useSingleVoice(input, [50, 51, 52], 2);\n  const hatVoice = useSingleVoice(input, [54,56,58], 2);\n\n  const hiTomVoice = useSingleVoice(input, [59], 2);\n  const midTomVoice = useSingleVoice(input, [57], 2);\n  const lowTomVoice = useSingleVoice(input, [55], 2);\n  const flrTomVoice = useSingleVoice(input, [53], 2);\n\n  const kickModPitch = useMonoSignal();\n  subtract(settings.kickAttackPitch, settings.kickResonantPitch, kickModPitch);\n\n  const kickPitchTime = useMonoSignal();\n  multiply(kickVoice.velocity, useConstantBuffer(50 * MS_TO_SAMPLES), kickPitchTime);\n  add(kickPitchTime, useConstantBuffer(100 * MS_TO_SAMPLES), kickPitchTime);\n\n  DrumVoice({\n    ...kickVoice,\n    wavetable: kickWavetable,\n    output: kick,\n    basePitch: settings.kickResonantPitch,\n    modPitch: kickModPitch,\n    wtTime: settings.kickWavetableScanTime,\n    pitchTime: settings.kickPitchDropTime,\n    gainTime: settings.kickAmplitudeDecayTime,\n    filterTime: settings.kickFilterDropTime,\n  });\n\n  add(output[0], kick[0], output[0]);\n  add(output[1], kick[1], output[1]);\n\n  const snareSkin1 = useStereoSignal();\n  snareSkin1[0].fill(0);\n  snareSkin1[1].fill(0);\n\n  DrumVoice({\n    ...snareVoice,\n    wavetable: snareWavetable,\n    output: snareSkin1,\n    basePitch: settings.snareSkin1Pitch,\n    modPitch: useConstantBuffer(.6),\n    wtTime: settings.snareSkin1WavetableScanTime,\n    pitchTime: useConstantBuffer(300 * MS_TO_SAMPLES),\n    gainTime: settings.snareSkin1Decay,\n    filterTime: settings.snareSkin1Decay,\n  });\n\n  multiply(snareSkin1[0], settings.snareSkin1Level, snareSkin1[0]);\n  multiply(snareSkin1[1], settings.snareSkin1Level, snareSkin1[1]);\n  add(snare[0], snareSkin1[0], snare[0]);\n  add(snare[1], snareSkin1[1], snare[1]);\n\n  const snareSkin2 = useStereoSignal();\n\n  const ringPitch = useMonoSignal();\n  add(settings.snareSkin2Offset, settings.snareSkin1Pitch, ringPitch);\n  snareSkin2[0].fill(0);\n  snareSkin2[1].fill(0);\n\n  DrumVoice({\n    ...snareVoice,\n    wavetable: snareWavetable,\n    output: snareSkin2,\n    basePitch: ringPitch,\n    modPitch: useConstantBuffer(.75),\n    wtTime: settings.snareSkin2WavetableScanTime,\n    pitchTime: useConstantBuffer(400 * MS_TO_SAMPLES),\n    gainTime: settings.snareSkin2Decay,\n    filterTime: settings.snareSkin2Decay,\n  });\n\n  multiply(snareSkin2[0], settings.snareSkin2Level, snareSkin2[0]);\n  multiply(snareSkin2[1], settings.snareSkin2Level, snareSkin2[1]);\n\n  add(snare[0], snareSkin2[0], snare[0]);\n  add(snare[1], snareSkin2[1], snare[1]);\n\n  const snareSnares = useStereoSignal();\n  snareSnares[0].fill(0);\n  snareSnares[1].fill(0);\n  RattleVoice(snareVoice.trigger, snareVoice.gate, snareVoice.velocity, snareSnares, settings.snareSnaresAttack, settings.snareSnaresDecay);\n\n  multiply(snareSnares[0], settings.snareSnaresLevel, snareSnares[0]);\n  multiply(snareSnares[1], settings.snareSnaresLevel, snareSnares[1]);\n\n  add(snare[0], snareSnares[0], snare[0]);\n  add(snare[1], snareSnares[1], snare[1]);\n\n  add(output[0], snare[0], output[0]);\n  add(output[1], snare[1], output[1]);\n\n  const hatCymbalDecay = useMonoSignal();\n  const hatNoiseDecay = useMonoSignal();\n  const hatNoiseAttack = useMonoSignal();\n  const hatCymbalAttack = useMonoSignal();\n  for (let i = 0; i < hatCymbalDecay.length; i++) {\n    const timingSpec = hatPitchTiming[hatVoice.pitch[i]];\n    hatCymbalDecay[i] = timingSpec?.cymbalDecay || 0;\n    hatNoiseDecay[i] = timingSpec?.noiseDecay || 0;\n    hatNoiseAttack[i] = timingSpec?.noiseAttack || 0;\n    hatCymbalAttack[i] = timingSpec?.cymbalAttack || 0;\n  }\n  const hatCymbal = useStereoSignal();\n  hatCymbal[0].fill(0);\n  hatCymbal[1].fill(0);\n  CymbalVoice({\n    ...hatVoice,\n    wavetable: hatWavetable,\n    output: hatCymbal,\n    attackTime: hatCymbalAttack,\n    decayTime: hatCymbalDecay,\n    carrierRoot: settings.hatCarrierRoot,\n    carrierScale: settings.hatCarrierScale,\n    carrier1Delta: settings.hatCarrierDelta1,\n    carrier2Delta: settings.hatCarrierDelta2,\n    carrier3Delta: settings.hatCarrierDelta3,\n    carrier4Delta: settings.hatCarrierDelta4,\n    modulator1: settings.hatModulator1Freq,\n    modulator2: settings.hatModulator2Freq,\n    band1Freq: settings.hatCarrierBandpass1Freq,\n    band2Freq: settings.hatCarrierBandpass2Freq,\n    band1Res: settings.hatCarrierBandpass1Res,\n    band2Res: settings.hatCarrierBandpass2Res,\n  });\n  const hatNoise = useStereoSignal();\n  hatNoise[0].fill(0);\n  hatNoise[1].fill(0);\n  RattleVoice(hatVoice.trigger, hatVoice.gate, hatVoice.velocity, hatNoise, hatNoiseAttack, hatNoiseDecay);\n  for (let i = 0; i < hatNoise[0].length; i ++) {\n    const timingSpec = hatPitchTiming[hatVoice.pitch[i]];\n    const noiseGain = timingSpec?.noiseGain || 0;\n    const cymbalGain = timingSpec?.cymbalGain || 0;\n    hatNoise[0][i] *= noiseGain;\n    hatNoise[1][i] *= noiseGain;\n    hatCymbal[0][i] *= cymbalGain;\n    hatCymbal[1][i] *= cymbalGain;\n  }\n  add(hat[0], hatNoise[0], hat[0]);\n  add(hat[1], hatNoise[1], hat[1]);\n  add(hat[0], hatCymbal[0], hat[0]);\n  add(hat[1], hatCymbal[1], hat[1]);\n\n  add(output[0], hat[0], output[0]);\n  add(output[1], hat[1], output[1]);\n\n  const tomPitchTime = useMonoSignal();\n  multiply(hiTomVoice.velocity, useConstantBuffer(50 * MS_TO_SAMPLES), tomPitchTime);\n  add(tomPitchTime, useConstantBuffer(100 * MS_TO_SAMPLES), tomPitchTime);\n\n  for (let i = 0; i < 4; i ++) {\n    let voice, currentOutput, resonantPitchBuffer;\n    if (i === 0) {\n      voice = hiTomVoice;\n      currentOutput = hiTom;\n      resonantPitchBuffer = settings.hiTomResonantPitch;\n    } else if (i === 1) {\n      voice = midTomVoice;\n      currentOutput = midTom;\n      resonantPitchBuffer = settings.midTomResonantPitch;\n    } else if (i === 2) {\n      voice = lowTomVoice;\n      currentOutput = lowTom;\n      resonantPitchBuffer = settings.lowTomResonantPitch;\n    } else if (i === 3) {\n      voice = flrTomVoice;\n      currentOutput = flrTom;\n      resonantPitchBuffer = settings.flrTomResonantPitch;\n    }\n\n    DrumVoice({\n      ...voice,\n      wavetable: tomWavetable,\n      output: currentOutput,\n      basePitch: resonantPitchBuffer,\n      modPitch: settings.tomModPitch,\n      wtTime: settings.tomWavetableScanTime,\n      pitchTime: settings.tomPitchDropTime,\n      gainTime: settings.tomAmplitudeDecayTime,\n      filterTime: settings.tomFilterDropTime,\n    });\n\n    add(output[0], currentOutput[0], output[0]);\n    add(output[1], currentOutput[1], output[1]);\n  }\n}\n\nconst WAVETABLES = {\n  Kick: '[encoded data, 11456 characters]',\n  Snare: '[encoded data, 5380 characters]',\n  Crash: 'ZSTDKLUv/WBKCPVJANSRCQCCAIAng4KI7oyXjDCFlHfFZzVbSlZiWj5l+HHdelZ7V3EGXsJFWy9+IlklQzo5XkeJ5rCfy0/Um8vutqydG4Zhc2xlZVqiUAxIGUK8QPpE502pWHFh62R3YbFXGkoUPL0wIyr/KPgsKjWqQLtOwV4TcNGB6pJWom2vLLo6w63LktRy3v/oGvMT+yP/3/2G9iXpkNZBwCCoOJBRepNnVlg2TINC3DqlNQc0VTccQEpN+ltFaL5uHW4VaNNgQV2+YGRrz3lzhg2ME4jkezNsGl/NWehe/m0shE+dOrWLyBfVDNrY1+fPQMQAt+Cp9p27k1GLxoQ/gMx9OX3offN+nX/AfwCAANiBi4FWf6p7BnficbRs/mdhZIliGWN+Ztls8nVHgSqO1ZuHqZu2ncJKzXnW790U49fk2uHo2KHJArV7nW6GPHM+Zh1g1V8pY1xn5mn9aNdjnlpOTnhA+TK0J0kg1h29IH8oxDN8QE5MPlVOWu1bAVxPXaFi0202f3+VSq7LxoPcqO03+cL+Of7W9xzsBNwQyS+1f6LhkqWHQ4FJf5KAf4NMhmmHwIXmgEF56G9mZkxevlgrVjpW8FcJWlFb6VpzWDdUC08dSr1G60UDSJdMlFLEWEVe8mJZZ1psnHIaehiCW4mwjlWRNJHZjjaLUIf+g8KBuYClgA6BboFngd2AAID/HwCA1YEnhLCGO4mqi+KNvI/8kFyRrJDijjmMNYmnhpuFIocJjJGULKCJrdO6KcYLzrfRMNENzULGyr2DtASrkqEymMOONoW3e+VyvWtKZzxmfmgWbWlypXZZeNh2VnK/a11kgF1FWHZVllXlWGlf+mhGdeaDWJQRpmm4jcp22wjqPvVt/Hr/0/5B+4b1A+6J5HjYIMkntuefhIe3bnlXpEOgNC8rUydZKO8scDNKOl1AQEVBSRBNSVEeViJbfF9YYk1jpGJSYcJgb2KTZ8Vwwn09jQOdXKq9spC0sq+NpaOYz4trgc16L3jaeJ97Nn+NgvCEGIYghl+FPoQPg/qB+T8AgACEKYbahv6G2oenikWQ9JgppKGwlrwtxvLLVs3WytzFUMAAvCa6L7vSvj7EacpS0B7VLtgm2e3XiNTuzgTHhbxKr32fto0qe3dpQVrCTo9HcESDRIVGEkn0SmhLWUp0SApHwkcbTPFUMmLfcliFy5ekqN+2GcKByq3QadV52VLd/eD441zlH+Rt3+/W5MoVvKCrsJpOijZ7xG3yYWlXuU2ARLU7zDOeLT8qqSpyL5E4TkVZVOhjA3LafAuD8IPIf7R3fm0uY7BaZlX4Uz9WcltCYjZp826DcoRzLHJBb91rMmlLaMZpuW2ic5R6aoEgh/6KvIyGjM2KLYg9hW6CXwCAkoA9gtSEWoj6jPKSX5oeo7asbLZlv9PGDMykzl7OP8uExa69f7T1qhWi55oullWUWJW2mHudZaL/pfmmbqQknqaUHYkIffxxVWkVZKxiB2Wlaqdy9XtlheiNnZTzmLGa65n/ln+SJI3AhxuD+H/vfmaAloR3i8CU3Z/6qwG4zcJLy7PQrNJZ0TXN88ZLv9O26a24pE6bqZHDh7B9mHPHaaFgnFg4UuhNBUy7TAdQo1UfXeZlb29UeW+D0Y2gmO6jdq+aunbECsx90DzRJs6ax2m+wbP8qFuft5ddkhWPQ40qjDaLGYrViJuHq4Y8hleG6Yaxh1mIhYjjh0OGmIN/AIBpfp985nqHebp4mngleTN6i3vyfDR+QH8hgAyBRYIEhF6GLokfjM+O4pArkrKSoZIsknORdpAWjyKNb4rfhnyCgH1SeIRzwG+lbaVt5m9DdFZ6jYFHifGQG5iFniWkIanFrX+yxLcEvoLFRs4Q2E/iK+y59Cb73P6i/5r9Lvnw8nbrQ+PC2kPS/skNwm66+LJiq06jYZpgkEmFZ3k7bXph7VZKTiJIz0RvRNpGr0tpUnVaXWO9bGB2GoC/iQ6TqpsloxCpMK1/rxywQa8crdmplqWJoACbc5VlkFqMpIlUiD6I+Yj9ibuKwYrLidSHGoUKgih/7Hyne3l7THzmfZ8AgBmBwYGqgaWAqn7le7F4h3XhciFxdnDhcCty/3MCduF3bHmqesd7H30Rf+aBvIV1irSP75R1maSc1Z2DnGmYfZEGiI980W+zYj9WfktpQ8c+Kj7BQVlJaFQPYktxG4GkkFSf2awfuTzEY87D137goegN8H32ld3/NP6O+cHx1eYC2ajIbbYpo9uPl31abfJf2lU6T+5LtUs0TiFTQFpgY0ZusnpIiJyWKaVrs9fA8sxZ19bfVeb46vPtme848Pjv5e4U7YrqKufP4nbdIdcB0GzIuMBJuXOybaxGp+SiCZ9om7aXz5Osj3SLaYfPg+iA136jfTd9aX0Gful+vwCAh4PvhR6HCYewhTSDx3+3e1p3+nLHbtNqFWdsY8dfF1xsWPdU/1HVT9JOMk8VUWNU6FhPXkhkkGoGcYx3FH6IhMSKqJAOlt2a/J55onClIqjoqiauPrJzt9a9SMV+zQfWXd795WzsRvFG9ET1PvRU8b3syubN3xTY3c9Tx4a+bLXmq8Sh1Jbxigh+KXCNYYxSnEM8Ne0nHBweEjYKiAQlARMAUgHeBLwK8BKCHXIqqjn6Sg5eYHI8h8WbEa9CwKbOuNkx4S7lFuZ25Ong/dsl1qrPrsgywTS5wbD/pzSft5bljhOIgYJOfnh75XlheaZ5b3p+e598q32Nfjp/tH/fAIBef05+3HwRe/h4mHYFdFlxr24pbOZpAmiEZnhlzmR+ZHFkkmTZZDtltmVLZutmiWcHaE9oSWjxZ19nuGZBZlhmTmd/aTFthnKAefCBiIvtlbqggavgtYm/M8inz7zVXtqI3UHfmt+u3q/c0tlH1jnSt821yArDirwHtWissqILmLyMFoGDdWhqGWDUVr1O3EcbQl09gjlyNhs0dzKLMWExCjKeMzo2BDocP6NFuE1sV8Jiq2/2fVGNS51grQi9wssG2VDkVu3t8/z3efls+PH0K+9I55LdYNIbxj25Say+nwqUfolbgMR4yXJrbqFrWGpyasdrI25JcfN003ibfP+7DduDbRJtuzfhPIqX4sudsUgKKJBDuwOQ2+8BAVPANCz4GfAUMAVsCpB04GCc7T8E',\n\n   'Tom': '[encoded data, 5528 characters]'\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  noteInputHint: {\n    interface: NoteInterfaceType.Pads,\n    usedPitches: {\n      48: 'Kick',\n      50: 'Snare L',\n      51: 'Snare Ghost',\n      52: 'Snare R',\n      54: 'Hat Closed',\n      56: 'Hat Pedal',\n      58: 'Hat Open',\n    }\n  },\n  implementation: DigitalDrums,\n  constants: {\n    kickWavetable: wavetableConstant('Kick Wavetable', WAVETABLES.Kick),\n    snareWavetable: wavetableConstant('Snare Wavetable', WAVETABLES.Snare),\n    hatWavetable: wavetableConstant('Hat Wavetable', WAVETABLES.Crash),\n    tomWavetable: wavetableConstant('Tom Wavetable', WAVETABLES.Tom)\n  },\n  inputs: {\n    input: midi('In'),\n\n    kickResonantPitch: numberSetting('Kick Resonant Pitch', { min: 10, max: 30, defaultValue: 22 }),\n    kickAttackPitch: numberSetting('Kick Attack Pitch', { min: 40, max: 55, defaultValue: 43.5 }),\n    kickWavetableScanTime: adsrTimeSetting('Kick Wavetable Scan Time', 220),\n    kickPitchDropTime: adsrTimeSetting('Kick Pitch Drop Time', 150),\n    kickAmplitudeDecayTime: adsrTimeSetting('Kick Amplitude Decay Time', 1400),\n    kickFilterDropTime: adsrTimeSetting('Kick Filter Drop Time', 92),\n\n    snareSkin1Pitch: numberSetting('Snare Skin 1 Pitch', { min: 30, max: 60, defaultValue: 42.8 }),\n    snareSkin1Level: dbSetting('Snare Skin 1 Level', { defaultValue: -4, max: 0, min: -24 }),\n    snareSkin1WavetableScanTime: adsrTimeSetting('Snare Skin 1 Wavetable Scan Time', 30),\n    snareSkin1Decay: adsrTimeSetting('Snare Skin 1 Decay', 230),\n\n    snareSkin2Offset: numberSetting('Snare Skin 2 Offset', { min: 5, max: 9, defaultValue: 7.48 }),\n    snareSkin2Level: dbSetting('Snare Skin 2 Level', { defaultValue: -15, max: -3, min: -30 }),\n    snareSkin2WavetableScanTime: adsrTimeSetting('Snare Skin 2 Wavetable Scan Time', 30),\n    snareSkin2Decay: adsrTimeSetting('Snare Skin 2 Decay', 230),\n\n    snareSnaresLevel: dbSetting('Snare Snares Level', { defaultValue: 0 }),\n    snareSnaresAttack: adsrTimeSetting('Snare Snares Attack', 17),\n    snareSnaresDecay: adsrTimeSetting('Snare Snares Decay', 190),\n\n    hatCarrierDelta1: numberSetting('Hat Carrier Delta 1', { min: 0, max: 100, defaultValue: 1.6 }),\n    hatCarrierDelta2: numberSetting('Hat Carrier Delta 2', { min: 0, max: 100, defaultValue: 15.8 }),\n    hatCarrierDelta3: numberSetting('Hat Carrier Delta 3', { min: 0, max: 100, defaultValue: 31.8 }),\n    hatCarrierDelta4: numberSetting('Hat Carrier Delta 4', { min: 0, max: 100, defaultValue: 51.0 }),\n    hatCarrierRoot: numberSetting('Hat Carrier Root', { min: 900, max: 1100, defaultValue: 949 }),\n    hatCarrierScale: numberSetting('Hat Carrier Scale', { min: 1, max: 5, defaultValue: 3 }),\n    hatModulator1Freq: numberSetting('Hat Modulator 1 Freq', { min: 2000, max: 8000, defaultValue: 4000 }),\n    hatModulator2Freq: numberSetting('Hat Modulator 2 Freq', { min: 2000, max: 8000, defaultValue: 5000 }),\n    hatCarrierBandpass1Freq: numberSetting('Hat Carrier BPF 1 Freq', { min: 2000, max: 8000, defaultValue: 5000 }),\n    hatCarrierBandpass2Freq: numberSetting('Hat Carrier BPF 2 Freq', { min: 2000, max: 8000, defaultValue: 6500 }),\n    hatCarrierBandpass1Res: numberSetting('Hat Carrier BPF 1 Res', { min: 0.1, max: 5, defaultValue: 1 }),\n    hatCarrierBandpass2Res: numberSetting('Hat Carrier BPF 2 Res', { min: 0.1, max: 5, defaultValue: 2 }),\n\n    hiTomResonantPitch: numberSetting('High Tom Resonant Pitch', { min: 25, max: 60, defaultValue: 50 }),\n    midTomResonantPitch: numberSetting('Mid Tom Resonant Pitch', { min: 25, max: 60, defaultValue: 46 }),\n    lowTomResonantPitch: numberSetting('Low Tom Resonant Pitch', { min: 25, max: 60, defaultValue: 40 }),\n    flrTomResonantPitch: numberSetting('Floor Tom Resonant Pitch', { min: 25, max: 60, defaultValue: 34 }),\n\n    tomModPitch: numberSetting('Tom Modulated Pitch', { min: 0, max: 12, defaultValue: 6 }),\n    tomWavetableScanTime: adsrTimeSetting('Tom Wavetable Scan Time', 670),\n    tomPitchDropTime: adsrTimeSetting('Tom Pitch Drop Time', 220),\n    tomAmplitudeDecayTime: adsrTimeSetting('Tom Amplitude Decay Time', 338),\n    tomFilterDropTime: adsrTimeSetting('Tom Filter Drop Time', 110),\n  },\n  outputs: {\n    output: stereo('Out'),\n    kick: collapse(stereo('Kick')),\n    snare: collapse(stereo('Snare')),\n    hat: collapse(stereo('Hat')),\n    hiTom: collapse(stereo('High Tom')),\n    midTom: collapse(stereo('Mid Tom')),\n    lowTom: collapse(stereo('Low Tom')),\n    flrTom: collapse(stereo('Floor Tom')),\n  },\n  layout: row([\n    col([\n      box(\n        col([\n          spacer(5),\n          label('Hat'),\n          spacer(5),\n          row([\n            col([\n              row([spacer(40), label('Carrier')]),\n              spacer(10),\n              row([\n                box(label('Root'), 40),\n                box(linear('hatCarrierRoot'), 132, 20, gray300, 4),\n              ]),\n              spacer(4),\n              row([\n                box(label('Delta'), 40),\n                col([\n                  row([\n                    knob('hatCarrierDelta1', { size: 30 }),\n                    spacer(4),\n                    knob('hatCarrierDelta2', { size: 30 }),\n                    spacer(4),\n                    knob('hatCarrierDelta3', { size: 30 }),\n                    spacer(4),\n                    knob('hatCarrierDelta4', { size: 30 }),\n                  ])\n                ]),\n              ]),\n              spacer(4),\n              row([\n                box(label('Mult'), 40),\n                box(linear('hatCarrierScale'), 132, 20, gray300, 4),\n              ])\n            ]),\n            spacer(4),\n            col([\n              row([label('BPF')]),\n              spacer(4),\n              knob('hatCarrierBandpass1Freq', { size: 40 }),\n              spacer(4),\n              knob('hatCarrierBandpass2Freq', { size: 40 }),\n            ]),\n            spacer(4),\n            col([\n              row([label('Mod')]),\n              spacer(4),\n              knob('hatModulator1Freq', { size: 40 }),\n              spacer(4),\n              knob('hatModulator2Freq', { size: 40 }),\n            ]),\n            spacer(4),\n            col([\n              row([label('Pres.')]),\n              spacer(4),\n              knob('hatCarrierBandpass1Res', { size: 40 }),\n              spacer(4),\n              knob('hatCarrierBandpass2Res', { size: 40 }),\n            ]),\n            spacer(3),\n          ]),\n          spacer(5),\n        ]),\n        310,\n        'auto',\n        gray200,\n        8\n      ),\n      spacer(1),\n      box(\n        col([\n          spacer(5),\n          label('Snare'),\n          row([\n            spacer(5),\n            box(label('Skin 1'), 40, 'auto'),\n            spacer(2),\n            titled.knob('snareSkin1Level', 'Level', 30),\n            box(titled.knob('snareSkin1Pitch', 'Pitch', 30), 60, 'auto'),\n            col([\n              row([\n                box(label('Timbre'), 50, 20),\n                box(linear('snareSkin1WavetableScanTime'), 90, 20, gray300, 4),\n              ]),\n              spacer(2),\n              row([\n                box(label('Mute'), 50, 20),\n                box(linear('snareSkin1Decay'), 90, 20, gray300, 4)\n              ]),\n              spacer(2),\n            ]),\n            spacer(4),\n          ]),\n          row([\n            spacer(5),\n            box(label('Skin 2'), 40, 'auto'),\n            spacer(2),\n            titled.knob('snareSkin2Level', 'Level', 30),\n            box(titled.knob('snareSkin2Offset', 'Shift', 30), 60, 'auto'),\n            col([\n              row([\n                box(label('Timbre'), 50, 20),\n                box(linear('snareSkin2WavetableScanTime'), 90, 20, gray300, 4),\n              ]),\n              spacer(2),\n              row([\n                box(label('Mute'), 50, 20),\n                box(linear('snareSkin2Decay'), 90, 20, gray300, 4)\n              ]),\n              spacer(2),\n            ]),\n            spacer(4),\n          ]),\n          row([\n            spacer(5),\n            box(label('Snares'), 40, 'auto'),\n            spacer(2),\n            titled.knob('snareSnaresLevel', 'Level', 30),\n            box(spacer(0), 60, 'auto'),\n            col([\n              row([\n                box(label('Attack'), 50, 20),\n                box(linear('snareSnaresAttack'), 90, 20, gray300, 4),\n              ]),\n              spacer(2),\n              row([\n                box(label('Decay'), 50, 20),\n                box(linear('snareSnaresDecay'), 90, 20, gray300, 4)\n              ]),\n              spacer(2),\n            ]),\n            spacer(4),\n          ])\n        ]),\n        310,\n        'auto',\n        gray200,\n        8\n      ),\n      spacer(1),\n      box(\n        col([\n          spacer(5),\n          label('Kick'),\n          row([\n            titled.knob('kickResonantPitch', 'Res. Pitch', 60),\n            titled.knob('kickAttackPitch', 'Atk. Pitch', 60),\n            col([\n              row([\n                box(label('Pitch'), 50, 20),\n                box(linear('kickPitchDropTime'), 90, 20, gray300, 4),\n              ]),\n              spacer(2),\n              row([\n                box(label('Timbre'), 50, 20),\n                box(linear('kickWavetableScanTime'), 90, 20, gray300, 4)\n              ]),\n              spacer(2),\n              row([\n                box(label('Clarity'), 50, 20),\n                box(linear('kickFilterDropTime'), 90, 20, gray300, 4)\n              ]),\n              spacer(2),\n              row([\n                box(label('Mute'), 50, 20),\n                box(linear('kickAmplitudeDecayTime'), 90, 20, gray300, 4)\n              ]),\n            ]),\n            spacer(4),\n          ])\n        ]),\n        310,\n        'auto',\n        gray200,\n        8\n      ),\n    ]),\n    col([\n      box(\n        col([\n          spacer(5),\n          label('Toms'),\n          row([\n            col([\n              row([\n                knob('hiTomResonantPitch', { size: 30 }),\n                spacer(4),\n                knob('midTomResonantPitch', { size: 30 }),\n                spacer(4),\n                knob('lowTomResonantPitch', { size: 30 }),\n                spacer(4),\n                knob('flrTomResonantPitch', { size: 30 }),\n              ]),\n              spacer(4),\n              box(label('Resonant Pitches'), 120, 20, gray100, 4),\n            ]),\n            titled.knob('tomModPitch', 'Mod. Pitch', 30),\n            col([\n              row([\n                box(label('Pitch'), 50, 20),\n                box(linear('tomPitchDropTime'), 90, 20, gray300, 4),\n              ]),\n              spacer(2),\n              row([\n                box(label('Timbre'), 50, 20),\n                box(linear('tomWavetableScanTime'), 90, 20, gray300, 4)\n              ]),\n            ]),\n            col([\n              row([\n                box(label('Clarity'), 50, 20),\n                box(linear('tomFilterDropTime'), 90, 20, gray300, 4)\n              ]),\n              spacer(2),\n              row([\n                box(label('Mute'), 50, 20),\n                box(linear('tomAmplitudeDecayTime'), 90, 20, gray300, 4)\n              ]),\n            ]),\n            spacer(4),\n          ])\n        ]),\n        310,\n        'auto',\n        gray200,\n        8\n      ),\n    ]),\n  ]),\n  presetTags: {\n    'Genre': [\n      'Funk',\n      'Pop',\n      'Metal',\n      'Drum & Bass'\n    ]\n  }\n});\n\n	74	2023-03-24 15:09:51.368124	62b798fb-15b5-4268-8c22-72b821c9411d
13	Amplifier	1	const Effect = ({ input, output, gain }) => {\n  for (let i = 0; i < input.length; i++) {\n    output[i] = input[i] * dbToMultiplier(gain[i]);\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Effect,\n  inputs: {\n    input: stereo('In'),\n    gain: numberSetting('Gain', { defaultValue: 0, min: -35, max: 35, unit: InterfaceUnit.LiteralDecibels }),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	\N	2023-03-24 15:39:00.071572	aa2e8a0c-bfe7-42e9-8271-2f296198c1c2
14	Master Fader	1	const Effect = ({ input, output, gain }) => {\n  for (let i = 0; i < input.length; i++) {\n    output[i] = input[i] * gain[i];\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Effect,\n  inputs: {\n    input: stereo('In'),\n    gain: dbSetting('Master Volume', { defaultValue: 0, min: -Infinity, max: 6 }),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	\N	2023-03-24 15:39:12.467252	0c1291d9-cc0a-4d2f-949c-81e1c3e25106
15	Stereo Inverter	1	const Effect = ({ input, output }) => {\n  for (let i = 0; i < input.length; i++) {\n    output[i] = input[i] * -1;\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Effect,\n  inputs: {\n    input: stereo('In'),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	\N	2023-03-24 15:40:05.268061	3a47386c-541a-4507-991b-56b8c228d621
16	EQ Band	1	const FFT_SIZE = 8192;\nconst BLOCK_SIZE = 2048;\n\nconst FADE_RATE = BLOCK_SIZE / 50000;\nconst RECOVER_RATE = BLOCK_SIZE / 200;\n\nconst EQ_BAND_MODES = {\n  'EQ Band': BiQuadFilterMode.EQBand,\n  'Low Pass': BiQuadFilterMode.LowPass,\n  'High Pass': BiQuadFilterMode.HighPass,\n  'Band Pass': BiQuadFilterMode.BandPass,\n  'Notch': BiQuadFilterMode.Notch,\n  'Low Shelf': BiQuadFilterMode.LowShelf,\n  'High Shelf': BiQuadFilterMode.HighShelf,\n}\n\nconst EQBand = ({ input, output, analysis, frequency, q, adaptiveQ, effectiveQ, gain, mode, interfaceVisible }) => {\n  const filterLeft = useBiQuadFilter();\n  const filterRight = useBiQuadFilter();\n\n  const adjustedQ = useMonoSignal(q.length);\n\n  if (adaptiveQ && mode === 'EQ Band') {\n    for (let i = 0; i < q.length; i ++) {\n      adjustedQ[i] = q[i] + (Math.abs(Math.log(gain[i])));\n    }\n  } else {\n    adjustedQ.set(q);\n  }\n\n  effectiveQ[0] = adjustedQ[0];\n\n  output[0].set(filterLeft(input[0], EQ_BAND_MODES[mode], frequency, adjustedQ, gain));\n  output[1].set(filterRight(input[1], EQ_BAND_MODES[mode], frequency, adjustedQ, gain));\n\n  const lastAnalysisResult = useMonoSignal(analysis.length);\n  const nextAnalysisResult = useMonoSignal(analysis.length);\n  const fft = useFFT(FFT_SIZE, BLOCK_SIZE);\n  const stepIndex = useVal(0);\n\n  if (!interfaceVisible) return;\n\n  for (let i = 0; i < output[0].length; i ++) {\n    fft.receiveSample((output[0][i] + output[1][i]) / sampleRate);\n    stepIndex.current ++;\n    if (fft.isProcessingStep()) {\n      fft.updateSpectrum();\n      stepIndex.current = 0;\n      lastAnalysisResult.set(nextAnalysisResult);\n      nextAnalysisResult.set(fft.getSpectrumOutput());\n    }\n  }\n\n  for (let a = 0; a < analysis.length; a ++) {\n    const progress = (stepIndex.current / BLOCK_SIZE);\n    const value = (\n      (lastAnalysisResult[a] * (1 - progress)) +\n      (nextAnalysisResult[a] * progress)\n    );\n    const faded = analysis[a] * (1-FADE_RATE);\n    analysis[a] = Math.max(\n      faded,\n      faded + (RECOVER_RATE * value)\n    )\n  }\n}\n\nconst Spectrum = (ctx, { analysis, frequency, effectiveQ, gain, mode }) => {\n  const w = ctx.canvas.width;\n  const h = ctx.canvas.height;\n  const focusH = h - 14;\n  // const imageData = ctx.getImageData(0, 0, w, h);\n  ctx.clearRect(0, 0, w, h);\n  // ctx.putImageData(imageData, 1, -1);\n  ctx.fillStyle = '#000000'\n  ctx.fillRect(0, 0, w, h);\n\n  ctx.strokeStyle = '#444';\n  ctx.lineWidth = 1;\n  renderSpectrumGrid(ctx, w, focusH);\n\n  const maxDB = 24;\n  const minDB = -24;\n\n  for (let i = maxDB; i >= minDB; i -= 12) {\n    ctx.beginPath();\n    const y = ((i - maxDB) / (minDB - maxDB)) * focusH;\n    ctx.moveTo(0, y);\n    ctx.lineTo(w, y);\n    ctx.stroke();\n    ctx.closePath();\n  }\n\n  ctx.fillStyle = '#666';\n  ctx.font = `10px Arial`;\n  labelSpectrumGrid(ctx, w, h);\n\n  ctx.lineWidth = 1;\n  ctx.strokeStyle = '#ffffff44';\n  ctx.fillStyle = '#ffffff11';\n  ctx.beginPath();\n  ctx.moveTo(-w,h);\n\n  traceSpectrum(ctx, analysis, FFT_SIZE, sampleRate, w, focusH, 'line');\n\n  ctx.lineTo(w * 2,h);\n  ctx.stroke();\n  ctx.fill();\n  ctx.closePath();\n\n  ctx.lineWidth = 2;\n  ctx.strokeStyle = blue500;\n  ctx.fillStyle = `${blue500}44`;\n  ctx.beginPath();\n\n  const transferFunction = getBiQuadFilterTransferFunction(\n    sampleRate,\n    EQ_BAND_MODES[mode],\n    frequency,\n    effectiveQ[0],\n    multiplierToDb(gain)\n  );\n\n  const zeroY = ((0 - maxDB) / (minDB - maxDB)) * focusH;\n  ctx.moveTo(-w, zeroY);\n  let x, y;\n\n  for (let i = 0; i < transferFunction.length; i ++) {\n    x = i / (transferFunction.length - 1) * w;\n    y = (transferFunction[i][1] - maxDB) / (minDB - maxDB) * focusH;\n\n    ctx.lineTo(x, y);\n  }\n\n  ctx.lineTo(w * 2, zeroY);\n\n  ctx.stroke();\n  ctx.fill();\n  ctx.closePath();\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: EQBand,\n  inputs: {\n    input: stereo('In'),\n    q: numberSetting('Q', { defaultValue: Math.sqrt(.5), min: .1, max: 18, curve: Curve.Log2 }),\n    frequency: frequencySetting('Frequency', 1000),\n    gain: biQuadFilterDbSetting('Gain', { defaultValue: 0, min: -24, max: 24 })\n  },\n  constants: {\n    adaptiveQ: booleanConstant('Adaptive Q', true),\n    mode: enumConstant('Mode', Object.keys(EQ_BAND_MODES), 'EQ Band')\n  },\n  outputs: {\n    output: stereo('Out')\n  },\n  sharedArrays: {\n    analysis: FFT_SIZE / 2,\n    effectiveQ: 1,\n  },\n  layout: themed(DeviceLayoutTheme.Skeuomorphic, col([\n    pinnable(canvas(\n      'spectrum',\n      Spectrum,\n      344,\n      150\n    )),\n    row([\n      titled.checkbox('adaptiveQ', 'Adapt.'),\n      titled.knob('q'),\n      titled.knob('frequency', 'Freq'),\n      titled.knob('gain'),\n      titled.selector('mode')\n    ])\n  ]))\n});\n	\N	2023-03-24 15:40:34.852677	c2fc8823-c5a4-44c9-ae3f-29e132081462
17	Compressor	1	const Compressor = ({ input, output, detector, threshold, ratio: inverseRatio, attack, release, gain: gainDB, gainReduction, detectorLevel, makeupGain }) => {\n  const appliedGainRef = useVal(0);\n\n  for (let i = 0; i < input[0].length; i ++) {\n    const thresholdDB = threshold[i];\n    const ratio = 1/inverseRatio[i];\n\n    const makeupGainAmount = makeupGain ? (-thresholdDB) * ((1 - ratio) / 2) : 0;\n\n    let rawAmplitude = 0;\n\n    for (let c = 0; c < detector.length; c ++) {\n      const channelAmplitude = Math.abs(detector[c][i]);\n      if (channelAmplitude > rawAmplitude) {\n        rawAmplitude = channelAmplitude;\n      }\n    }\n\n    const amplitudeDB = multiplierToDb(rawAmplitude)\n\n    const attackRate = 1/attack[i];\n    const releaseRate = 1/release[i];\n\n    const targetGainDB = amplitudeDB > thresholdDB\n      ? (thresholdDB - amplitudeDB) * (1 - ratio)\n      : 0;\n\n    const gainDelta = targetGainDB < appliedGainRef.current ? attackRate : releaseRate;\n\n    appliedGainRef.current = ((1 - gainDelta) * appliedGainRef.current) + (gainDelta * targetGainDB);\n    gainReduction[i] = appliedGainRef.current;\n\n    const gain = dbToMultiplier(appliedGainRef.current + gainDB[i] + makeupGainAmount);\n\n    detectorLevel[0] = Math.max(\n      rawAmplitude,\n      detectorLevel[0]\n    ) * (1 - (7/sampleRate));\n\n    for (let c = 0; c < input.length; c ++) {\n      output[c][i] = input[c][i] * gain;\n    }\n  }\n}\n\nconst CornerUI = (ctx, settings) => {\n  const width = ctx.canvas.width;\n  const height = ctx.canvas.height;\n\n  ctx.fillStyle = gray100;\n  roundedRect(ctx, 1, 1, ctx.canvas.width - 2, ctx.canvas.height - 2, 4, false, true);\n  ctx.clip();\n  ctx.fill();\n\n  const threshold = settings.threshold;\n  const thresholdProportion = 1 - ((threshold - 12) / (-64 - 12));\n\n  const ratio = (settings.ratio);\n\n  const thresholdY = height * (1 - thresholdProportion);\n  const thresholdX = width * (thresholdProportion);\n\n  const ratioEndY = (thresholdY * (1 - (1/ratio)));\n\n  ctx.strokeStyle = gray300;\n\n  ctx.beginPath();\n  for (let i = 12; i >= -64; i -= 12) {\n    const y = ((i - 12) / (-64 - 12)) * height;\n    ctx.moveTo(0, y);\n    ctx.lineTo(width, y);\n\n    const x = (1 - ((i - 12) / (-64 - 12))) * width;\n    ctx.moveTo(x, 0);\n    ctx.lineTo(x, height);\n  }\n  ctx.stroke();\n\n  ctx.strokeStyle = gray600;\n\n  ctx.lineWidth = 2;\n  ctx.fillStyle = '#fff';\n\n  ctx.beginPath();\n  ctx.moveTo(0, height);\n  ctx.lineTo(thresholdX, thresholdY);\n  ctx.lineTo(width, ratioEndY);\n  ctx.stroke();\n\n  const detectorLevel = 1 - ((multiplierToDb(settings.detectorLevel) - 12) / (-64 - 12));\n  const outputLevel = detectorLevel > thresholdProportion\n    ? thresholdProportion + ((detectorLevel - thresholdProportion) / ratio)\n    : detectorLevel;\n\n  ctx.beginPath();\n  ctx.arc(\n    detectorLevel * width,\n    (1 - outputLevel) * height,\n    6,\n    0,\n    2 * Math.PI\n  );\n  ctx.fill();\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: Compressor,\n  inputs: {\n    input: stereo('In'),\n    detector: stereo('Detector'),\n    gain: numberSetting('Gain', { defaultValue: 0, min: -12, max: 12 }),\n    threshold: numberSetting('Threshold', { defaultValue: -20, min: -64, max: 0, unit: InterfaceUnit.LiteralDecibels }),\n    ratio: numberSetting('Ratio', { defaultValue: 8, min: 1, max: 64, curve: Curve.Log2 }),\n    attack: adsrTimeSetting('Attack', 1),\n    release: adsrTimeSetting('Release', 30),\n  },\n  constants: {\n    makeupGain: booleanConstant('Makeup Gain', false)\n  },\n  outputs: {\n    output: stereo('Out'),\n  },\n  hidden: {\n    gainReduction: dbSetting('Gain Reduction', {})\n  },\n  sharedArrays: {\n    detectorLevel: 1\n  },\n  layout: themed(\n    DeviceLayoutTheme.Skeuomorphic,\n    row([\n      meter('input', 4),\n      col([\n        titled.knob('threshold'),\n        titled.knob('ratio'),\n        titled.knob('gain')\n      ]),\n      spacer(.5),\n      canvas(\n        'corner',\n        CornerUI,\n        200,\n        200\n      ),\n      spacer(.5),\n      col([\n        titled.knob('attack'),\n        titled.knob('release'),\n        titled.checkbox('makeupGain', 'Makeup')\n      ]),\n      meter('output', 4, true),\n    ])\n  )\n});\n	\N	2023-03-24 15:41:07.702676	b00e53f2-a886-41b7-ba22-4b44fc203a92
18	Crossfade	1	const Crossfade = ({ input1, input2, output, crossfade }) => {\n  for (let i = 0; i < input1.length; i ++) {\n    output[i] = (input1[i] * (1 - crossfade[i])) + (input2[i] * crossfade[i])\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Crossfade,\n  inputs: {\n    input1: stereo('In 1'),\n    input2: stereo('In 2'),\n    crossfade: numberSetting('Crossfade', { defaultValue: 0.5, min: 0, max: 1, curve: Curve.Linear }),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	\N	2023-03-24 15:41:18.811717	d7f4d43d-5c31-4ece-a23d-b1d6f164476b
19	Reverb71	1	// Schroeder Reverb implementing the first diagram here: https://ccrma.stanford.edu/~jos/pasp/Example_Schroeder_Reverberators.html\nconst sampleRateMultiplier = (sampleRate / 25000);\n\nconst Reverb71 = ({ input, inputLevel, reverbLevel, output, time, mix, width }) => {\n  const reverb = useStereoSignal();\n\n  const allPass1 = useBiQuadFilter();\n  const allPass2 = useBiQuadFilter();\n  const allPass3 = useBiQuadFilter();\n\n  const delay1 = useDelay();\n  const delay2 = useDelay();\n  const delay3 = useDelay();\n  const delay4 = useDelay();\n\n  const monoInput = useMonoSignal();\n  const delay1Time = useMonoSignal();\n  const delay2Time = useMonoSignal();\n  const delay3Time = useMonoSignal();\n  const delay4Time = useMonoSignal();\n\n  for (let i = 0; i < time.length; i ++) {\n    delay1Time[i] = time[i] * sampleRateMultiplier * 901;\n    delay2Time[i] = time[i] * sampleRateMultiplier * 778;\n    delay3Time[i] = time[i] * sampleRateMultiplier * 1011;\n    delay4Time[i] = time[i] * sampleRateMultiplier * 1123;\n  }\n\n  for (let i = 0; i < input[0].length; i++) {\n    monoInput[i] = 0;\n    for (let c = 0; c < input.length; c ++) {\n      monoInput[i] += input[c][i];\n    }\n  }\n\n  const allpassed = allPass3(\n    allPass2(\n      allPass1(\n        monoInput,\n        BiQuadFilterMode.AllPass,\n        useConstantBuffer(125),\n        useConstantBuffer(.7),\n        useConstantBuffer(0)\n      ),\n      BiQuadFilterMode.AllPass,\n      useConstantBuffer(42),\n      useConstantBuffer(.7),\n      useConstantBuffer(0)\n    ),\n    BiQuadFilterMode.AllPass,\n    useConstantBuffer(12),\n    useConstantBuffer(.7),\n    useConstantBuffer(0)\n  );\n\n  const delayed1 = delay1(allpassed, delay1Time, useConstantBuffer(0.805));\n  const delayed2 = delay2(allpassed, delay2Time, useConstantBuffer(0.827));\n  const delayed3 = delay3(allpassed, delay3Time, useConstantBuffer(0.783));\n  const delayed4 = delay4(allpassed, delay4Time, useConstantBuffer(0.764));\n\n  for (let i = 0; i < input[0].length; i++) {\n    for (let c = 0; c < input.length; c ++) {\n      const widthScale = (2 * ((c / (input.length - 1)) - .5)) * width[i];\n      const delayed = (\n        (1 / 8) * (\n          (((widthScale * -1.00) + 1) / 2) * delayed1[i] +\n          (((widthScale * -0.33) + 1) / 2) * delayed2[i] +\n          (((widthScale *  0.33) + 1) / 2) * delayed3[i] +\n          (((widthScale *  1.00) + 1) / 2) * delayed4[i]\n        )\n      );\n      reverb[c][i] = delayed;\n      output[c][i] = delayed * mix[i] + input[c][i] * (1 - mix[i]);\n    }\n  }\n\n  writeSignalLevel(input, inputLevel);\n  writeSignalLevel(reverb, reverbLevel);\n};\n\nconst RenderReverbVis = (ctx, settings) => {\n  const w = ctx.canvas.width;\n  const h = ctx.canvas.height;\n  ctx.fillStyle = `${orange500}22`;\n  if (!ctx.hasRendered) {\n    ctx.hasRendered = true;\n    ctx.fillRect(0, 0, w, h);\n  }\n  const imgData = ctx.getImageData(1, 0, w-1, h);\n  ctx.putImageData(imgData, 0, 0);\n  ctx.clearRect(w-1, 0, 1, h);\n  ctx.fillRect(w-1, 0, 1, h);\n  const signalHeight = h/2;\n\n  const leftInputHeight = (settings.inputLevel[0]) * (signalHeight);\n  const rightInputHeight = (settings.inputLevel[0]) * (signalHeight);\n\n  ctx.fillStyle = `rgba(255,255,255,${settings.mix})`;\n  const leftOutputHeight = (settings.reverbLevel[0]) * (signalHeight);\n  const rightOutputHeight = (settings.reverbLevel[0]) * (signalHeight);\n  ctx.fillRect(\n    w - 1,\n    (h/2) - leftOutputHeight,\n    1,\n    leftOutputHeight + rightOutputHeight\n  );\n\n  ctx.strokeStyle = orange500;\n  ctx.beginPath();\n  ctx.moveTo(\n    w - 2,\n    ctx.lastLeft === null ? (h/2) : ctx.lastLeft\n  );\n  ctx.lineTo(\n    w - 1,\n    (h/2) - leftInputHeight,\n  );\n  ctx.closePath();\n  ctx.stroke();\n  ctx.lastLeft = (h/2) - leftInputHeight;\n\n  ctx.beginPath();\n  ctx.moveTo(\n    w - 2,\n    ctx.lastRight === null ? (h/2) : ctx.lastRight\n  );\n  ctx.lineTo(\n    w - 1,\n    (h/2) + rightInputHeight,\n  );\n  ctx.closePath();\n  ctx.stroke();\n  ctx.lastRight = (h/2) + rightInputHeight;\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: Reverb71,\n  inputs: {\n    input: stereo('In'),\n    time: numberSetting('Time', { defaultValue: 1, min: .1, max: 10, curve: Curve.Log2 }),\n    mix: numberSetting('Mix', { defaultValue: 1, min: 0, max: 1 }),\n    width: numberSetting('Width', { defaultValue: 1, min: 0, max: 1 })\n  },\n  outputs: {\n    output: stereo('Out'),\n  },\n  sharedArrays: {\n    inputLevel: 1,\n    reverbLevel: 1,\n  },\n  layout: themed(\n    DeviceLayoutTheme.Dark,\n    col([\n      canvas(\n        'visualizer',\n        RenderReverbVis,\n        250,\n        100\n      ),\n      spacer(0.25),\n      row([\n        titled.knob('time'),\n        titled.knob('mix'),\n        titled.knob('width')\n      ])\n    ])\n  )\n});\n	\N	2023-03-24 15:41:33.051737	c70556bf-0ba7-4283-a3cc-f2722ee390ec
20	Gate	1	const LOOKAHEAD_MS = 4;\nconst LOOKAHEAD_SAMPLES = Math.floor(LOOKAHEAD_MS * (sampleRate / 1000));\n\nconst Gate = ({ input, output, detector, threshold, floor, attack, release, hold }) => {\n  const appliedGainRef = useVal(0);\n  const peakHoldRef = useVal(0);\n\n  const delayedSignal = useDelayedStereo(input, LOOKAHEAD_SAMPLES);\n\n  for (let i = 0; i < delayedSignal[0].length; i ++) {\n    const thresholdDB = threshold[i];\n    const floorDB = floor[i];\n    const attackRate = 1/(attack[i] + LOOKAHEAD_MS);\n    const releaseRate = 1/release[i];\n    const holdRate = 1/hold[i];\n\n    let rawAmplitude = 0;\n\n    for (let c = 0; c < detector.length; c ++) {\n      const channelAmplitude = Math.abs(detector[c][i]);\n      if (channelAmplitude > rawAmplitude) {\n        rawAmplitude = channelAmplitude;\n      }\n    }\n\n    peakHoldRef.current = Math.max(peakHoldRef.current * (1 - holdRate), rawAmplitude);\n\n    const amplitudeDB = multiplierToDb(peakHoldRef.current)\n\n    let targetGainDB = amplitudeDB < thresholdDB\n      ? floorDB\n      : 0;\n\n    const gainDelta = targetGainDB < appliedGainRef.current ? releaseRate : attackRate;\n\n    appliedGainRef.current = Math.min(0, ((1 - gainDelta) * appliedGainRef.current) + (gainDelta * targetGainDB));\n\n    const gain = dbToMultiplier(appliedGainRef.current);\n\n    for (let c = 0; c < delayedSignal.length; c ++) {\n      output[c][i] = delayedSignal[c][i] * gain;\n    }\n  }\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: Gate,\n  inputs: {\n    input: stereo('In'),\n    detector: stereo('Detector'),\n    threshold: numberSetting('Threshold', { defaultValue: -20, min: -64, max: 0, unit: InterfaceUnit.LiteralDecibels }),\n    floor: numberSetting('Floor', { defaultValue: -40, min: -80, max: 0, unit: InterfaceUnit.LiteralDecibels }),\n    attack: adsrTimeSetting('Attack', 0.1),\n    hold: adsrTimeSetting('Hold', 10),\n    release: adsrTimeSetting('Release', 15),\n  },\n  outputs: {\n    output: stereo('Out', LOOKAHEAD_SAMPLES),\n  }\n});\n	\N	2023-03-24 15:41:49.577426	578b51d1-d2e4-4674-a8ca-135c0d097cf3
34	Channel Strip	1	const ChannelStripDevice = ({ input, output, gain, balance }) => {\n  for (let c = 0; c < input.length; c++) {\n    const inputChannel = input[c];\n    const outputChannel = output[c];\n    for (let s = 0; s < outputChannel.length; s++) {\n      const balanceMultiplier = c === 0\n        ? Math.min(1, -1 * (balance[s] - 1))\n        : Math.min(1, balance[s] + 1);\n      outputChannel[s] = inputChannel[s] * gain[s] * balanceMultiplier;\n    }\n  }\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: ChannelStripDevice,\n  inputs: {\n    input: stereo('In'),\n    gain: dbSetting('Gain', { defaultValue: 0, min: -Infinity, max: 12 }),\n    balance: numberSetting('Balance', { defaultValue: 0, min: -1, max: 1 }),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	\N	2023-03-24 15:46:19.868219	a19792b0-326f-4b82-93a8-2422ffe215b5
81	Sampler	1	const interpolatedRead = (buffer, index) => {\n  if (buffer.length <= 1) {\n    return 0;\n  }\n  const prev = Math.max(0, Math.floor(index));\n  const next = Math.min(buffer.length - 1, prev + 1);\n  const amountOfNextSample = index - prev;\n  const amountOfPrevSample = 1 - amountOfNextSample;\n  return buffer[prev] * amountOfPrevSample + buffer[next] * amountOfNextSample;\n};\n\nconst SampleVoice = (\n  { velocity, trigger, gate }, \n  { attack, decay, sustain, release, pitch, gain, pan, output, buffer },\n) => {\n\n  const processEnvelope = useEnvelope();\n  const amplitude = processEnvelope(trigger, gate, attack, decay, sustain, release);\n\n  multiply(amplitude, velocity, amplitude);\n  multiply(amplitude, gain, amplitude);\n\n  if (allEqual(amplitude, 0)) {\n    return;\n  }\n  const readIndex = useVal(0);\n  const isPlaying = useVal(false);\n\n  const voiceOutput = useStereoSignal();\n  const leftBuffer = buffer.channelDataList[0];\n\n  const panLeft = useMonoSignal();\n  const panRight = useMonoSignal();\n  multiply(pan, useConstantBuffer(-1), panLeft);\n  \n  add(panLeft, useConstantBuffer(1), panLeft);\n  clamp(panLeft, panLeft, 0, 1);\n  \n  add(pan, useConstantBuffer(1), panRight);\n  clamp(panRight, panRight, 0, 1);\n  \n  multiply(panLeft, amplitude, panLeft);\n  multiply(panRight, amplitude, panRight);\n\n  if (leftBuffer.length <= 1) return;\n\n  const rightBuffer = buffer.channelDataList.length === 1 ? leftBuffer : buffer.channelDataList[1];\n\n  voiceOutput[0].fill(0);\n  voiceOutput[1].fill(0);\n\n  for (let i = 0; i < output[0].length; i++) {\n    if (trigger[i] === 1) {\n      isPlaying.current = true;\n      readIndex.current = 0;\n    } else if (amplitude[i] === 0) {\n      isPlaying.current = false;\n    }\n\n    if (isPlaying.current) {\n      if (readIndex.current < (leftBuffer.length - 1)) {\n        voiceOutput[0][i] += interpolatedRead(leftBuffer, readIndex.current);\n        voiceOutput[1][i] += interpolatedRead(rightBuffer, readIndex.current);\n        readIndex.current += transposeSemitones(1, pitch[i]);\n      }\n    }\n  }\n\n  multiply(voiceOutput[0], panLeft, voiceOutput[0]);\n  multiply(voiceOutput[1], panRight, voiceOutput[1]);\n  add(output[0], voiceOutput[0], output[0]);\n  add(output[1], voiceOutput[1], output[1]);\n};\n\nconst getSampleData = (scope) => {\n  const results = [];\n  for (let i = 0; i < 12; i++) {\n    const pitches = [i + 36];\n    results.push({\n      pitches,\n      scope: {\n        attack: scope[`attack${i + 1}`],\n        decay: scope[`decay${i + 1}`],\n        sustain: scope[`sustain${i + 1}`],\n        release: scope[`release${i + 1}`],\n        pitch: scope[`pitch${i + 1}`],\n        gain: scope[`gain${i + 1}`],\n        pan: scope[`pan${i + 1}`],\n\n        buffer: scope[`sample${i + 1}`],\n        output: scope.output,\n      },\n    });\n  }\n  return results;\n};\n\nconst SamplerInstrument = (scope, globals) => {\n  scope.output[0].fill(0);\n  scope.output[1].fill(0);\n  const sampleData = useReturnedConst(getSampleData, scope);\n\n  for (let i = 0; i < sampleData.length; i++) {\n    const data = sampleData[i];\n    const voice = useSingleVoice(scope.input, data.pitches, 1);\n    const exit = enterChildScope();\n    SampleVoice(voice, data.scope);\n    exit();\n  }\n\n  multiply(scope.output[0], scope.gain, scope.output[0]);\n  multiply(scope.output[1], scope.gain, scope.output[1]);\n};\n\nconst sampleCol = (num) => {\n  const content = (color) => {\n    return col([\n      constantSetter('focusedSample', num, box(\n        audioBufferPicker(`sample${num}`),\n        100,\n        100,\n        color,\n        4\n      ))\n    ])\n  }\n  return conditional(\n    'focusedSample', \n    { [num]: content(gray250), _other: content(gray125) }\n  );\n};\n\nconst sampleSetting = (id, text) => box(\n  col([\n    knob(id),\n    spacer(5),\n    label(text)\n  ]),\n  50,\n  70\n);\n\nconst sampleSettings = (num) => col([\n  spacer(10),\n  row([\n    box(\n      row([\n        sampleSetting(`pitch${num}`, 'Pitch'),\n        spacer(20),\n        sampleSetting(`attack${num}`, 'Attack'),\n        sampleSetting(`decay${num}`, 'Decay'),\n        sampleSetting(`sustain${num}`, 'Sustain'),\n        sampleSetting(`release${num}`, 'Release'),\n        spacer(20),\n        sampleSetting(`pan${num}`, 'Pan'),\n        sampleSetting(`gain${num}`, 'Gain'),\n      ]),\n      440,\n      90,\n      gray200,\n      8\n    ),\n    spacer(100),\n    sampleSetting('gain', 'Master')\n  ])\n]);\n\nconst sampleSettingsPicker = () => {\n  const conditions = {};\n  for (let i = 1; i <= 12; i ++) {\n    conditions[i] = sampleSettings(i);\n  }\n  return conditional('focusedSample', conditions);\n}\n\nthis.constructDevice = () =>\n  constructPerBufferDevice({\n    noteInputHint: {\n      interface: NoteInterfaceType.Pads,\n      usedPitches: {\n        48: 'Sample 1',\n        49: 'Sample 2',\n        50: 'Sample 3',\n        51: 'Sample 4',\n\n        52: 'Sample 5',\n        53: 'Sample 6',\n        54: 'Sample 7',\n        55: 'Sample 8',\n\n        56: 'Sample 9',\n        57: 'Sample 10',\n        58: 'Sample 11',\n        59: 'Sample 12',\n      },\n    },\n    implementation: SamplerInstrument,\n    inputs: {\n      input: midi('In'),\n      gain: dbSetting('Gain', { defaultValue: -6 }),\n      ...new Array(12).fill(0).reduce(\n        (a, c, i) => ({\n          [`attack${i + 1}`]: adsrTimeSetting(`Attack ${i + 1}`, 1),\n          [`decay${i + 1}`]: adsrTimeSetting(`Decay ${i + 1}`, 100),\n          [`sustain${i + 1}`]: zeroOneSetting(`Sustain ${i + 1}`, 1),\n          [`release${i + 1}`]: adsrTimeSetting(`Release ${i + 1}`, 50),\n          [`gain${i + 1}`]: dbSetting(`Gain ${i + 1}`, 0),\n          [`pan${i + 1}`]: numberSetting(`Pan ${i + 1}`, { defaultValue: 0, min: -1, max: 1 }),\n          [`pitch${i + 1}`]: numberSetting(`Pitch ${i + 1}`, { defaultValue: 0, min: -24, max: 24 }),\n          ...a,\n        }),\n        {}\n      ),\n    },\n    outputs: {\n      output: stereo('Out'),\n    },\n    constants: {\n      sample1: audioBufferConstant('Sample 1'),\n      sample2: audioBufferConstant('Sample 2'),\n      sample3: audioBufferConstant('Sample 3'),\n      sample4: audioBufferConstant('Sample 4'),\n\n      sample5: audioBufferConstant('Sample 5'),\n      sample6: audioBufferConstant('Sample 6'),\n      sample7: audioBufferConstant('Sample 7'),\n      sample8: audioBufferConstant('Sample 8'),\n\n      sample9: audioBufferConstant('Sample 9'),\n      sample10: audioBufferConstant('Sample 10'),\n      sample11: audioBufferConstant('Sample 11'),\n      sample12: audioBufferConstant('Sample 12'),\n\n      focusedSample: enumConstant('Focused Sample', new Array(12).fill(0).map((_, i) => i), 'LFO 1'),\n    },\n    layout: col([\n      row([\n        sampleCol(7),\n        spacer(8),\n        sampleCol(8),\n        spacer(8),\n        sampleCol(9),\n        spacer(8),\n        sampleCol(10),\n        spacer(8),\n        sampleCol(11),\n        spacer(8),\n        sampleCol(12),\n      ]),\n      spacer(10),\n      row([\n        sampleCol(1),\n        spacer(8),\n        sampleCol(2),\n        spacer(8),\n        sampleCol(3),\n        spacer(8),\n        sampleCol(4),\n        spacer(8),\n        sampleCol(5),\n        spacer(8),\n        sampleCol(6),\n      ]),\n      spacer(10),\n      sampleSettingsPicker()\n    ]),\n    presetTags: {\n      'Instrument': [\n        'Drums',\n        'FX',\n        'Other'\n      ],\n      'Genre': [\n        'Ambient',\n        'Chill',\n        'Dubstep',\n        'EDM',\n        'Funk',\n        'Hip Hop',\n        'House',\n        'Jazz',\n        'Lo-Fi',\n        'Soundtrack',\n        'Metal',\n        'Pop',\n        'R&B',\n        'Rock',\n        'Techno',\n        'Trance',\n      ]\n    },\n  });\n	\N	2023-07-11 00:51:50.329977	c2fc1730-9cc9-4643-bb54-9435a920c927
21	Transient Splitter	1	const TransientSplitter = ({ input, attack, sustain, speed, sensitivity }) => {\n  const slowAmplitude = useVal(0);\n  const transientness = useVal(0);\n  const vuMeter = useVUFollower();\n\n  for (let i = 0; i < input[0].length; i ++) {\n    const fastAmplitude = Math.max(...input.map(channel => Math.abs(channel[i])));\n    slowAmplitude.current = vuMeter(fastAmplitude);\n\n    if ((fastAmplitude / slowAmplitude.current) > (1 - Math.pow(sensitivity[i] / 10, 2)) * 14) {\n      transientness.current = 1;\n    }\n\n    for (let c = 0; c < input.length; c ++) {\n      attack[c][i] = transientness.current * input[c][i];\n      sustain[c][i] = (1 - transientness.current) * input[c][i];\n    }\n\n    transientness.current *= 1 - Math.pow(10 - speed[i], -4);\n  }\n};\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: TransientSplitter,\n  inputs: {\n    input: stereo('In'),\n    speed: numberSetting('Speed', { defaultValue: 4, min: 0, max: 10 }),\n    sensitivity: numberSetting('Sensitivity', { defaultValue: 9, min: 0, max: 10 }),\n  },\n  outputs: {\n    attack: stereo('Attack'),\n    sustain: stereo('Sustain'),\n  }\n});\n	\N	2023-03-24 15:42:05.94488	ea865cea-c4ea-4501-8342-4778eb9c3251
22	Frequency Splitter	1	const FrequencySplitter = ({ input, low, high, q, frequency }) => {\n  const filter = useBiQuadFilter();\n  const filtered = filter(input, BiQuadFilterMode.LowPass, frequency, q, useConstantBuffer(0));\n  low.set(filtered);\n\n  for (let i = 0; i < input.length; i ++) {\n    high[i] = input[i] - low[i];\n  }\n};\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: FrequencySplitter,\n  inputs: {\n    input: stereo('In'),\n    q: numberSetting('Q', { defaultValue: 1, min: 1/16, max: 8, curve: Curve.Log2 }),\n    frequency: frequencySetting('Frequency', 1000),\n  },\n  outputs: {\n    high: stereo('High'),\n    low: stereo('Low'),\n  }\n});\n	\N	2023-03-24 15:42:24.576711	da2559f9-d3e1-440f-ae9f-41f5fae35d30
23	Amplitude Splitter	1	const fftSize = 512;\nconst windowSize = 64;\nconst fftSizeTimesTwo = fftSize * 2;\n\nconst getArray = () => new Array(fftSize).fill(0);\n\nconst AmplitudeSplitter = ({ input, soft, loud, attack, release, threshold }) => {\n  const loudProcessor = useFFT(fftSize, windowSize);\n  const softProcessor = useFFT(fftSize, windowSize);\n  const attackAccumulation = useVal(0);\n  const releaseAccumulation = useVal(0);\n  const multipliers = useReturnedConst(getArray);\n  for (let i = 0; i < input.length; i ++) {\n    loudProcessor.receiveSample(input[i]);\n    softProcessor.receiveSample(input[i]);\n\n    const loudOut = loudProcessor.getComplexOutput();\n    const softOut = softProcessor.getComplexOutput();\n\n    attackAccumulation.current += 1/attack[i];\n    releaseAccumulation.current += 1/release[i];\n\n    if (loudProcessor.isProcessingStep()) {\n      const attackRate = attackAccumulation.current;\n      const releaseRate = releaseAccumulation.current;\n\n      attackAccumulation.current = 0;\n      releaseAccumulation.current = 0;\n\n      for (let j = 0; j < fftSizeTimesTwo; j += 2) {\n        const multiplierIndex = j / 2;\n        const bias = 1 - ((j / fftSizeTimesTwo) * (j / fftSizeTimesTwo));\n        const magnitude = Math.sqrt(loudOut[j] * loudOut[j] + loudOut[j+1] * loudOut[j+1]) * (1/(1 + bias));\n\n        if (magnitude < threshold[i] && multipliers[multiplierIndex] > 0) {\n          multipliers[multiplierIndex] = Math.max(0, (1 - releaseRate) * multipliers[multiplierIndex]);\n        } else if (magnitude > threshold[i] && multipliers[multiplierIndex] < 1) {\n          multipliers[multiplierIndex] = Math.min(1, ((1 - attackRate) * multipliers[multiplierIndex]) + (attackRate));\n        }\n      }\n\n      for (let j = 0; j < fftSizeTimesTwo; j += 2) {\n        const multiplierIndex = j / 2;\n        loudOut[j]   *= multipliers[multiplierIndex];\n        loudOut[j+1] *= multipliers[multiplierIndex];\n\n        softOut[j]   *= (1 - multipliers[multiplierIndex]);\n        softOut[j+1] *= (1 - multipliers[multiplierIndex]);\n      }\n    }\n\n    loud[i] = loudProcessor.produceSample();\n    soft[i] = softProcessor.produceSample();\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: AmplitudeSplitter,\n  inputs: {\n    input: stereo('In'),\n\n    threshold: numberSetting('Threshold', { defaultValue: 2, min: 0.01, max: 100, curve: Curve.Log2 }),\n    attack: adsrTimeSetting('Attack', 10),\n    release: adsrTimeSetting('Release', 100),\n  },\n  outputs: {\n    loud: stereo('Loud', computeFFTDelay(fftSize, windowSize)),\n    soft: stereo('Quiet', computeFFTDelay(fftSize, windowSize)),\n  }\n});\n	\N	2023-03-24 15:42:39.749297	b82112a2-6920-40d1-adb1-7c59bf627d22
24	Overdrive125	1	const Overdrive125 = ({ input, output, pre, post }) => {\n  for (let i = 0; i < input.length; i ++) {\n    const x = input[i] * pre[i];\n    const absX = Math.abs(x);\n    const sign = x > 0 ? 1 : -1;\n    const postMultiplier = post[i];\n    if (absX < 1/3) {\n      output[i] = 2 * x * postMultiplier;\n    } else if (absX < 2/3) {\n      output[i] = ((3 - Math.pow((2 - (3 * absX)), 2)) / 3) * sign * postMultiplier;\n    } else {\n      output[i] = sign * postMultiplier;\n    }\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Overdrive125,\n  inputs: {\n    input: stereo('In'),\n    pre: dbSetting('Pre-Gain', { defaultValue: 16, min: -24, max: 24 }),\n    post: dbSetting('Post-Gain', { defaultValue: -3, min: -24, max: 24 })\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	\N	2023-03-24 15:43:22.924229	833b0ed8-575a-4dec-baa0-8d8ef7910aca
25	Distortion127	1	const Distortion127 = ({ input, output, pre, post }) => {\n  for (let i = 0; i < input.length; i ++) {\n    const x = input[i] * pre[i];\n    const absX = Math.abs(x);\n    const sign = x > 0 ? 1 : -1;\n    output[i] = sign * (1 - Math.pow(Math.E, -1 * absX)) * post[i];\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Distortion127,\n  inputs: {\n    input: stereo('In'),\n    pre: dbSetting('Pre-Gain', { defaultValue: 16, min: -24, max: 24 }),\n    post: dbSetting('Post-Gain', { defaultValue: -8, min: -24, max: 24 })\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	\N	2023-03-24 15:43:38.200503	f499e121-459f-4131-80d9-7ec6253d96f5
26	Tube122	1	const Tube122 = ({ input, output, pre, dist, q, post }) => {\n  const filter = useBiQuadFilter();\n  for (let i = 0; i < input.length; i ++) {\n    const x = input[i] * pre[i];\n    if (q[i] === 0) {\n      if (x === 0) {\n        output[i] = 1/dist[i];\n      } else {\n        output[i] = x / (1 - Math.exp(-dist[i] * x));\n      }\n    } else {\n      if (x === q[i]) {\n        output[i] = 1 / dist[i] + q / (1 - Math.exp(dist[i] * q[i]));\n      } else {\n        output[i] = (x - q[i]) / (1 - Math.exp((-dist[i] * (x - q[i])))) + q[i] / (1 - Math.exp(dist[i] * q[i]));\n      }\n    }\n  }\n  const filtered = filter(output, BiQuadFilterMode.HighPass, useConstantBuffer(20), useConstantBuffer(1), useConstantBuffer(0));\n  for (let i = 0; i < output.length; i ++) {\n    output[i] = filtered[i] * post[i];\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Tube122,\n  inputs: {\n    input: stereo('In'),\n    pre: dbSetting('Pre-Gain', { defaultValue: 16, min: -24, max: 24 }),\n    dist: numberSetting('Dist', { defaultValue: 1, min: 1, max: 10 }),\n    q: numberSetting('Q', { defaultValue: -.1, min: -2, max: 2 }),\n    post: dbSetting('Post-Gain', { defaultValue: -12, min: -24, max: 24 }),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	\N	2023-03-24 15:43:49.562946	9709355c-f346-4a34-a4cf-1e6c6eabec81
27	Audio Sum	1	const AudioSum = ({ inputs, output }) => {\n  const inputArray = Object.values(inputs);\n\n  output.fill(0);\n\n  for (let i = 0; i < inputArray.length; i++) {\n    const inputChannel = inputArray[i];\n    for (let s = 0; s < inputChannel.length; s++) {\n      output[s] += inputChannel[s];\n    }\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: AudioSum,\n  inputs: {\n    inputs: repeatable(stereo('In #'))\n  },\n  outputs: {\n    output: stereo('Out')\n  }\n});\n	\N	2023-03-24 15:44:00.317468	ec86ba8c-4336-4856-8a70-cf0a74a4b423
29	Simple Visualizer	1	const MeterInputs = ({ input, inputMeter }) => {\n  writeSignalLevel(input, inputMeter, 3);\n}\n\nconst Visualizer = (ctx, settings, { clock, playing }) => {\n  ctx.fillStyle = gray100;\n  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);\n  const inputLevel = (settings.inputMeter[0]);\n  if (playing) {\n    const beatProgress = clock % 1;\n    if (beatProgress < 0.5) {\n      ctx.fillStyle = `rgba(255,255,255,${settings.brightness + (0.5 - beatProgress)})`;\n    } else {\n      ctx.fillStyle = `rgba(255,255,255,${settings.brightness})`;\n    }\n  } else {\n    ctx.fillStyle = `rgba(255,255,255,${settings.brightness})`;\n  }\n\n  ctx.beginPath();\n  ctx.arc(ctx.canvas.width/2, ctx.canvas.height/2, inputLevel * ctx.canvas.width/4, 0, 2 * Math.PI);\n  ctx.fill();\n};\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: MeterInputs,\n  inputs: {\n    input: stereo('In'),\n    brightness: expose(zeroOneSetting('Brightness', .5))\n  },\n  sharedArrays: {\n    inputMeter: 1\n  },\n  layout: row([\n    titled.knob('brightness'),\n    pinnable(canvas(\n      'main',\n      Visualizer,\n      200,\n      200\n    ))\n  ])\n});\n	\N	2023-03-24 15:44:45.010775	4de3d617-87f6-4273-93fa-6e052bf3c691
30	Simple Synth	1	const NUMBER_OF_VOICES = 8;\nconst VOICE_SCALE = 1 + Math.sqrt(NUMBER_OF_VOICES);\nconst vibratoFrequency = new Float64Array(bufferSize).fill(5);\n\nconst SimpleSynth = ({ input, attack, decay, sustain, release, transpose, output }) => {\n  output.fill(0);\n  const voices = useRoundRobinVoicing(input, NUMBER_OF_VOICES);\n\n  const vibratoOscillator = useOscillator(Waveform.Sine);\n  const vibratoOutput = vibratoOscillator(vibratoFrequency);\n\n  for (let v = 0; v < voices.length; v ++) {\n    const oscillator = useOscillator(Waveform.Sawtooth, true);\n    const processEnvelope = useEnvelope();\n    const gate = voices[v].gate;\n    const trigger = voices[v].trigger;\n    const frequency = useMonoSignal(voices[v].frequency.length);\n    for (let i = 0; i < frequency.length; i ++) {\n      frequency[i] = (\n        voices[v].frequency[i] *\n        Math.pow(\n          2,\n          (\n            input.ccs.pitchBend[i] +\n            (vibratoOutput[i] * (input.ccs.modulationWheel[i] + input.ccs.channelPressure[i]))\n          ) / 12\n        )\n      );\n    }\n    const velocity = voices[v].velocity;\n    const oscillatorOutput = oscillator(frequency);\n    const amplitude = processEnvelope(trigger, gate, attack, decay, sustain, release);\n    for (let i = 0; i < oscillatorOutput.length; i ++) {\n      output[i] += oscillatorOutput[i] * amplitude[i] * velocity[i] / VOICE_SCALE;\n    }\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: SimpleSynth,\n  inputs: {\n    input: midi('In'),\n    attack: adsrTimeSetting('Attack', 1),\n    decay: adsrTimeSetting('Decay', 100),\n    sustain: zeroOneSetting('Sustain', 0.5),\n    release: adsrTimeSetting('Release', 50),\n    transpose: numberSetting('Transpose', { defaultValue: 0, min: -1, max: 1 })\n  },\n  outputs: {\n    output: stereo('Out')\n  }\n});\n\n	\N	2023-03-24 15:45:03.261505	d694ef91-e624-404d-8e34-829d9c1c04b3
31	Oscilloscope	1	const phaseInput = new Float64Array(bufferSize);\nconst ReadOscilloscopeInputs = ({ input, frequency, trigger, inputOscilloscope }) => {\n  const lastWriteIndex = useVal(0);\n  const oscillator = useOscillator(Waveform.Sawtooth);\n  const oscillatorOutput = oscillator(frequency, phaseInput, trigger);\n  const lastOscillatorOutput = useVal(-1);\n  for (let i = 0; i < input[0].length; i ++) {\n    if (lastWriteIndex.current < inputOscilloscope.length) {\n      inputOscilloscope[lastWriteIndex.current] = input[0][i];\n      lastWriteIndex.current ++\n    } else if (lastOscillatorOutput.current <= 0 && oscillatorOutput[i] > 0) {\n      lastWriteIndex.current = 0;\n    }\n    lastOscillatorOutput.current = oscillatorOutput[i];\n  }\n}\n\nconst Visualizer = (ctx, settings) => {\n  const h = ctx.canvas.height;\n  const w = ctx.canvas.width;\n  const imageData = ctx.getImageData(0, 0, w, h);\n  ctx.clearRect(0, 0, w, h);\n  ctx.putImageData(imageData, 0, -1);\n  ctx.fillStyle = `${gray100}42`;\n  let max = 0;\n  for (let i = 0; i < settings.inputOscilloscope.length; i ++) {\n    max = Math.max(max, Math.abs(settings.inputOscilloscope[i]));\n  }\n  ctx.strokeStyle = `rgba(\n    ${Math.floor((settings.frequency + 80) % 255)},\n    ${Math.floor((settings.frequency + 160) % 255)},\n    ${Math.floor(settings.frequency % 255)},\n    ${max}\n  )`;\n\n  //  ctx.fillRect(0, 0, w, h);\n  ctx.beginPath();\n  ctx.moveTo(-w, h);\n  ctx.lineTo(-w, 9*h/10);\n  for (let i = 0; i < settings.inputOscilloscope.length; i ++) {\n    ctx.lineTo(\n      (i / settings.inputOscilloscope.length) * w,\n      9*h/10 + (settings.inputOscilloscope[i] * 0.2 * h/2)\n    );\n  }\n  ctx.lineTo(w*2, h/2);\n  ctx.lineTo(w*2, h);\n  ctx.closePath();\n  ctx.stroke();\n  ctx.fill();\n};\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: ReadOscilloscopeInputs,\n  inputs: {\n    input: stereo('In'),\n    frequency: expose(frequencySetting('Frequency', 1000)),\n    trigger: expose(zeroOneSetting('Trigger', 0)),\n  },\n  sharedArrays: {\n    inputOscilloscope: 1024\n  },\n  layout: row([\n    pinnable(canvas(\n      'main',\n      Visualizer,\n      400,\n      200\n    ))\n  ])\n});\n	\N	2023-03-24 15:45:14.380856	7dd325be-63d7-4dc4-8c0a-d43f49e53c5c
32	Psychedelic Christmas	1	const beatsPerRotation = 8;\nconst numberOfRotationalBars = 120;\nconst numberOfBackgroundBars = 40;\n\nconst MeterInputs = ({ white, green, red, whiteLevel, greenLevel, redLevel }) => {\n  writeSignalLevel(white, whiteLevel);\n  writeSignalLevel(red, redLevel);\n  writeSignalLevel(green, greenLevel);\n}\n\nconst colors = {\n  'whiteLevel': 'white',\n  'redLevel': 'red',\n  'greenLevel': 'green',\n};\n\nconst Visualizer = (ctx, settings) => {\n  if (!ctx.flakes || !ctx.bars) {\n    ctx.flakes = [];\n    ctx.bars = {};\n  }\n  const now = Date.now();\n  const rotation = ((numberOfRotationalBars * (now / 1000) * (100/60) / beatsPerRotation)) % numberOfRotationalBars;\n  const roundedRotation = Math.floor(rotation);\n  const w = ctx.canvas.width;\n  const h = ctx.canvas.height;\n  const minD = Math.min(w, h);\n  ctx.fillStyle = gray100;\n  ctx.fillRect(0, 0, w, h);\n\n  if (!ctx.bars[roundedRotation]) {\n    ctx.bars[roundedRotation] = {};\n  }\n\n  ['redLevel', 'greenLevel', 'whiteLevel'].forEach((c, i) => {\n    if (!ctx.bars[roundedRotation][c]) {\n      ctx.bars[roundedRotation][c] = {};\n    }\n    ctx.bars[roundedRotation][c].m = Math.pow(settings[c], 2);\n    ctx.bars[roundedRotation][c].t = now;\n  });\n\n  for (let i = 0; i < numberOfBackgroundBars; i ++) {\n    const m = (1 + (Math.sin((i/numberOfBackgroundBars) * 2 * Math.PI + (now / 1000)))) / 4 + 1/4;\n    ctx.fillStyle = gray150;\n    ctx.fillRect(\n      (w * (1/numberOfBackgroundBars) * i) + 1,\n      h/2,\n      (w * 1/numberOfBackgroundBars) - 2,\n      (h/2) * m\n    );\n    ctx.fillRect(\n      (w * (1/numberOfBackgroundBars) * i) + 1,\n      h/2 * (1 - m),\n      (w * 1/numberOfBackgroundBars) - 2,\n      (h/2) * m\n    );\n  }\n\n  Object.keys(ctx.bars).forEach((r) => {\n    ctx.save();\n    ctx.translate(w/2, h/2);\n    ctx.rotate((Number(r) / numberOfRotationalBars) * Math.PI * 2);\n    ctx.translate(-w/2, -h/2);\n    Object.keys(ctx.bars[r]).forEach((c, i, l) => {\n      const stackDepth = (l.length - i);\n      const beamWidth = stackDepth * (minD / 500);\n      ctx.fillStyle = colors[c];\n      const barHeight = (\n        ((minD * ctx.bars[r][c].m) / 4)\n        *\n        (1 - Math.max(0, Math.min(1, (now - ctx.bars[r][c].t) / 4000)))\n      ) + beamWidth;\n      if (barHeight >= beamWidth) {\n        ctx.beginPath();\n        const xOffset = Math.sin(i + (now / 1000)) * (minD / 20);\n        const yOffset = Math.cos((i * .5) + (now / 1000)) * (minD / 20);\n        roundedRect(\n          ctx,\n          xOffset + (w/2) - beamWidth,\n          yOffset + (\n            (minD/2) +\n            (stackDepth * minD/8) -\n            barHeight / 2\n          ),\n          beamWidth * 2,\n          barHeight,\n          beamWidth\n        );\n        // don't wobble around:\n        // roundedRect(\n        //   ctx,\n        //   (w/2) - beamWidth,\n        //   (\n        //     (minD/2) +\n        //     (stackDepth * minD/8) -\n        //     barHeight / 2\n        //   ),\n        //   beamWidth * 2,\n        //   barHeight,\n        //   beamWidth\n        // );\n        ctx.fill();\n      }\n    });\n    ctx.restore();\n  });\n\n  if (Math.random() < settings.snow) {\n    ctx.flakes.push({\n      v: 0.4,\n      x: Math.random() * w,\n      c: 0,\n      y: 0\n    });\n  }\n\n  ctx.fillStyle = '#fff';\n\n  ctx.flakes.forEach((f, i) => {\n    ctx.fillRect(f.x, f.y, 2, 2);\n    f.y += f.v;\n    f.x += Math.sin(((i * 90) + (f.y * 5) + Date.now()) / 1000) / 8\n  });\n\n  for (let i = ctx.flakes.length - 1; i >= 0; i --) {\n    if (ctx.flakes[i].y >= h) {\n      ctx.flakes.splice(i, 1);\n    }\n  }\n};\n\nthis.constructDevice = () => constructPerBufferDevice({\n  // no audio processing.\n  implementation: MeterInputs,\n  inputs: {\n    white: stereo('White'),\n    green: stereo('Green'),\n    red: stereo('Red'),\n    snow: zeroOneSetting('Snowiness', 0.1)\n  },\n  sharedArrays: {\n    whiteLevel: 1,\n    greenLevel: 1,\n    redLevel: 1,\n  },\n  layout: row([\n    titled.knob('snow'),\n    pinnable(canvas(\n      'main',\n      Visualizer,\n      200,\n      200\n    ))\n  ])\n});\n	\N	2023-03-24 15:45:29.36953	541a7bcc-0f34-4828-80fe-62bb81e6043e
35	Master Bus	1	const MasterSum = ({ inputs, output }) => {\n  const inputArray = Object.values(inputs);\n\n  output.fill(0);\n\n  for (let i = 0; i < inputArray.length; i++) {\n    const inputChannel = inputArray[i];\n    for (let s = 0; s < inputChannel.length; s++) {\n      output[s] += inputChannel[s];\n    }\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: MasterSum,\n  outputs: {\n    output: stereo('Out')\n  },\n  hidden: {\n    inputs: { type: 'Stereo', name: 'In #', repeatable: true }\n  }\n});\n	\N	2023-03-24 15:46:35.925688	66cff321-ae21-444d-a5dc-7c428a4fba25
36	Wavetable Synth	1	const WARP_MODES = {\n  Off: WavetableWarpMode.Off,\n  Bend: WavetableWarpMode.Bend,\n  Sync: WavetableWarpMode.Sync,\n}\n\nconst MODULATION_MODES = ['Off', 'FM', 'AM', 'Ring'];\n\nconst SUB_WAVEFORMS = {\n  'Saw': Waveform.Sawtooth,\n  'Sine': Waveform.Sine,\n  'Square': Waveform.Square,\n}\n\nconst FILTER_TYPES = {\n  'Low Pass': BiQuadFilterMode.LowPass,\n  'High Pass': BiQuadFilterMode.HighPass,\n  'Band Pass': BiQuadFilterMode.BandPass,\n  'Notch': BiQuadFilterMode.Notch,\n}\n\nconst usePitchModulator = (pitch, minPitch, maxPitch) => {\n  const result = useMonoSignal();\n  const range = useMonoSignal();\n  subtract(pitch, minPitch, result);\n  subtract(maxPitch, minPitch, range);\n  divide(result, range, result);\n  return result;\n}\n\nconst useVelocityModulator = (velocity, velocityCurve) => {\n  const result = useMonoSignal();\n  for (let i = 0; i < velocity.length; i ++) {\n    result[i] = Math.pow(velocity[i], Math.pow(2, velocityCurve[i] * -2))\n  }\n  return result;\n}\n\nconst useMultipliedEnvelope = (curve, amplitude, trigger, gate, attack, decay, sustain, release) => {\n  return multiply(\n    amplitude,\n    useEnvelope(curve === 'Linear' ? Curve.Linear : Curve.Log2)(trigger, gate, attack, decay, sustain, release),\n    useMonoSignal()\n  );\n}\n\nconst LFO_BEAT_FRACTIONS = [\n  32,\n  24,\n  16,\n  12,\n  8,\n  6,\n  4,\n  3,\n  2,\n  1.5,\n  4/3,\n  1,\n  3/4,\n  2/3,\n  1/2,\n  1/3,\n  1/4,\n  1/6,\n  1/8,\n  1/12,\n  1/16,\n  1/24,\n  1/32\n];\n\nconst useSyncFrequency = (frequency, bpm) => {\n  const syncedFrequency = useMonoSignal(frequency.length);\n  for (let i = 0; i < syncedFrequency.length; i ++) {\n    syncedFrequency[i] = (1/frequency[i]) * (bpm[i] / 60);\n  }\n  return syncedFrequency;\n}\n\nconst useMultipliedLFO = (amplitude, waveform, frequency, phase, trigger) => {\n  return multiply(\n    amplitude,\n    useOscillator(waveform)(frequency, phase, trigger),\n    useMonoSignal()\n  );\n}\n\nconst usePortamento = (pitch, portamento, trigger, gate) => {\n  const result = useMonoSignal();\n  if (result[0] === 0) {\n    result.fill(pitch[0]);\n  }\n  const lastGate = useVal(gate[0]);\n  for (let i = 0; i < pitch.length; i ++) {\n    if (trigger[i] > 0.5 && lastGate.current < 0.5) {\n      result.fill(pitch[i], i);\n    } else {\n      const curvedPortamento = Math.pow(portamento[i], .25);\n      result[i] = (\n        (result[i] * curvedPortamento) + (pitch[i] * (1 - curvedPortamento))\n      );\n    }\n    lastGate.current = gate[i];\n  }\n  return result;\n}\n\nconst useOscillatorSection = (\n  focal,\n  retrigger,\n  trigger,\n  pitch,\n  unison,\n  spread,\n  width,\n  wavetable,\n  wtPos,\n  warpMode,\n  warp,\n  phase,\n  phaseRand,\n  transpose,\n  pan,\n  level,\n  feedback\n) => {\n  const output = useStereoSignal();\n\n  output[0].fill(0);\n  output[1].fill(0);\n\n  const randomPhases = [];\n  for (let u = 0; u < unison; u ++) {\n    randomPhases[u] = useMonoSignal();\n    randomPhases[u].fill(randomPhases[u][randomPhases[u].length - 1]);\n  }\n\n  for (let i = 0; i < trigger.length; i ++) {\n    if (trigger[i] > 0.5) {\n      for (let u = 0; u < randomPhases.length; u ++) {\n        randomPhases[u].fill(((Math.random() * 2) - 1) * Math.PI, i);\n      }\n    }\n  }\n\n  for (let u = 0; u < unison; u ++) {\n    const voicePosition = unison === 1\n      ? 0\n      : (((u / (unison - 1)) * 2) - 1);\n    const voicePitchPosition = Math.floor(unison/2) % 2 === 0 ? voicePosition : -voicePosition;\n    const voiceFrequency = useMonoSignal();\n\n    for (let i = 0; i < pitch.length; i ++) voiceFrequency[i] = pitchToHz(pitch[i] + (voicePitchPosition * spread[i]) + transpose[i]);\n\n    const voicePhase = useMonoSignal();\n    for (let i = 0; i < phase.length; i ++) voicePhase[i] = phase[i] + (randomPhases[u][i] * phaseRand[i]);\n\n    const oscillator = useWavetableReader(wavetable, warpMode, feedback);\n    const monoSignal = oscillator(voiceFrequency, wtPos, warp, voicePhase, retrigger ? trigger : useMonoSignal(), focal && u === 0);\n    const leftLevel = useMonoSignal();\n    const rightLevel = useMonoSignal();\n\n    for (let i = 0; i < monoSignal.length; i ++) {\n      const pRange = (1 - Math.abs(pan[i]));\n      const effectivePan = pan[i] + ((voicePosition * width[i]) * pRange);\n      leftLevel[i] = Math.min(1, (-1 * (effectivePan - 1)))\n      rightLevel[i] = Math.min(1, (effectivePan + 1))\n    }\n\n    const leftOutput = useMonoSignal();\n    const rightOutput = useMonoSignal();\n\n    multiply(monoSignal, leftLevel, leftOutput);\n    multiply(monoSignal, rightLevel, rightOutput);\n    multiply(leftOutput, level, leftOutput);\n    multiply(rightOutput, level, rightOutput);\n    add(output[0], leftOutput, output[0]);\n    add(output[1], rightOutput, output[1]);\n  }\n  return output;\n}\n\nconst getUsedModulators = (matrix) => {\n  // envelope1 is hard-coded to control voice amplitude.\n  const usedModulators = {\n    envelope1: true\n  };\n  Object.keys(matrix).forEach((destination) => {\n    Object.keys(matrix[destination]).forEach((k) => {\n      usedModulators[k] = true;\n    });\n  });\n  return usedModulators;\n}\n\nconst Wavetable = (s, g) => {\n  const usedModulators = useReturnedConst(getUsedModulators, s.matrix)\n  for (let c = 0; c < s.output.length; c ++) s.output[c].fill(0);\n\n  const voices = useRoundRobinVoicing(s.input, s.voiceCount, s.retrigger);\n  for (let v = 0; v < voices.length; v ++) {\n    const exit = enterChildScope();\n\n    // Do the once-per-voice things.\n    const voice = voices[v];\n    const focal = s.interfaceVisible && voice.focal;\n    const trigger = voice.trigger;\n    const gate = voice.gate;\n    const portamentoPitch = usePortamento(voice.pitch, s.portamento, trigger, gate);\n\n    const modulators = {};\n\n    modulators.velocity = useVelocityModulator(voice.velocity, s.velocityCurve);\n    modulators.pitch = usePitchModulator(portamentoPitch, s.minPitch, s.maxPitch);\n    modulators.modWheel = s.input.ccs.modulationWheel;\n\n    const mPre1 = useModulationMatrix(\n      s.matrix,\n      modulators,\n      s\n    );\n\n    // envelope1 is always active.\n    modulators.envelope1 = useMultipliedEnvelope(s.env2Curve, mPre1.envAmplitude1, trigger, gate, mPre1.attack1, mPre1.decay1, mPre1.sustain1, mPre1.release1);\n\n    if (voice.alive && allEqual(modulators.envelope1, 0)) {\n      voice.alive = false;\n    }\n\n    if (!voice.alive && !focal) {\n      exit();\n      continue;\n    }\n\n    modulators.envelope2 = usedModulators.envelope2 ? useMultipliedEnvelope(s.env2Curve, mPre1.envAmplitude2, trigger, gate, mPre1.attack2, mPre1.decay2, mPre1.sustain2, mPre1.release2) : useMonoSignal();\n\n    modulators.envelope3 = usedModulators.envelope3 ? useMultipliedEnvelope(s.env3Curve, mPre1.envAmplitude3, trigger, gate, mPre1.attack3, mPre1.decay3, mPre1.sustain3, mPre1.release3) : useMonoSignal();\n\n    modulators.envelope4 = usedModulators.envelope4 ? useMultipliedEnvelope(s.env4Curve, mPre1.envAmplitude4, trigger, gate, mPre1.attack4, mPre1.decay4, mPre1.sustain4, mPre1.release4) : useMonoSignal();\n\n    const mPre2 = useModulationMatrix(\n      s.matrix,\n      modulators,\n      s\n    );\n\n    const frequency1 = s.sync1 === 'Sync' ? useSyncFrequency(mPre2.frequency1, g.bpm) : mPre2.frequency1;\n    const frequency2 = s.sync2 === 'Sync' ? useSyncFrequency(mPre2.frequency2, g.bpm) : mPre2.frequency2;\n    const frequency3 = s.sync3 === 'Sync' ? useSyncFrequency(mPre2.frequency3, g.bpm) : mPre2.frequency3;\n\n    modulators.lfo1 = usedModulators.lfo1 ? useMultipliedLFO(mPre2.lfoAmplitude1, SUB_WAVEFORMS[s.waveform1], frequency1, mPre2.phase1, trigger, g.bpm, s.sync1) : useMonoSignal();\n    modulators.lfo2 = usedModulators.lfo2 ? useMultipliedLFO(mPre2.lfoAmplitude2, SUB_WAVEFORMS[s.waveform2], frequency2, mPre2.phase2, trigger, g.bpm, s.sync2) : useMonoSignal();\n    modulators.lfo3 = usedModulators.lfo3 ? useMultipliedLFO(mPre2.lfoAmplitude3, SUB_WAVEFORMS[s.waveform3], frequency3, mPre2.phase3, trigger, g.bpm, s.sync3) : useMonoSignal();\n\n    const m = useModulationMatrix(\n      s.matrix,\n      modulators,\n      s\n    );\n\n    const allOnes = useMonoSignal();\n    allOnes.fill(1);\n\n    const bOutput = s.oscBEnabled ? useOscillatorSection(\n      focal,\n      s.retrigger,\n      trigger,\n      portamentoPitch,\n      s.unisonB,\n      m.spreadB,\n      m.widthB,\n      s.wavetableB,\n      m.wtPosB,\n      WARP_MODES[s.warpModeB],\n      m.warpB,\n      m.phaseB,\n      m.phaseRandB,\n      m.transposeB,\n      m.panB,\n      s.modulationMode === 'Off' ? m.levelB : allOnes,\n      s.waveformB,\n    ) : useStereoSignal();\n\n    const fmPitch = useMonoSignal();\n    const amLevel = useMonoSignal();\n    const bOutputMono = useMonoSignal();\n\n    if (s.modulationMode !== 'Off') {\n      add(bOutput[0], bOutput[1], bOutputMono);\n    }\n\n    if (s.modulationMode === 'FM') {\n      const fmScale = useMonoSignal();\n      fmScale.fill(60);\n      const fmModulator = useMonoSignal();\n      multiply(fmScale, m.modulationAmount, fmScale);\n      multiply(bOutputMono, fmScale, fmModulator);\n      add(portamentoPitch, fmModulator, fmPitch);\n    } else {\n      fmPitch.set(portamentoPitch);\n    }\n\n    if (s.modulationMode === 'AM') {\n      const amModulator = useMonoSignal();\n      const offsetModulator = useMonoSignal();\n      const offset = useMonoSignal();\n      offset.fill(1);\n      const multiplier = useMonoSignal();\n      multiplier.fill(0.5);\n      add(bOutputMono, offset, offsetModulator); // 0 - 2\n      multiply(offsetModulator, multiplier, amModulator); // 0 - 1\n      multiply(m.modulationAmount, amModulator, amModulator);\n      add(m.levelA, amModulator, amLevel);\n    } else if (s.modulationMode === 'Ring') {\n      const amModulator = useMonoSignal();\n      multiply(m.modulationAmount, bOutputMono, amModulator);\n      multiply(m.levelA, amModulator, amLevel);\n    } else {\n      amLevel.set(m.levelA);\n    }\n\n    const aOutput = s.oscAEnabled ? useOscillatorSection(\n      focal,\n      s.retrigger,\n      trigger,\n      fmPitch,\n      s.unisonA,\n      m.spreadA,\n      m.widthA,\n      s.wavetableA,\n      m.wtPosA,\n      WARP_MODES[s.warpModeA],\n      m.warpA,\n      m.phaseA,\n      m.phaseRandA,\n      m.transposeA,\n      m.panA,\n      amLevel,\n      s.waveformA,\n    ) : useStereoSignal();\n\n    const subFrequency = useMonoSignal();\n    const totalSubTransposition = add(portamentoPitch, m.transposeSub, useMonoSignal());\n    for (let i = 0; i < subFrequency.length; i ++) subFrequency[i] = pitchToHz(totalSubTransposition[i]);\n\n    const subOscillator = useOscillator(SUB_WAVEFORMS[s.subWaveform], s.subEnabled);\n    const subOutput = subOscillator(subFrequency, useConstantBuffer(0), trigger);\n    const multipliedSubOutput = s.subEnabled ? multiply(subOutput, m.subLevel, useMonoSignal()) : useMonoSignal();\n\n    const leftFilter = useBiQuadFilter();\n    const rightFilter = useBiQuadFilter();\n\n    const preFilterOutput = useStereoSignal();\n    const filterBypassOutput = useStereoSignal();\n\n    preFilterOutput[0].fill(0);\n    preFilterOutput[1].fill(0);\n\n    filterBypassOutput[0].fill(0);\n    filterBypassOutput[1].fill(0);\n\n    if (s.filterA) {\n      add(preFilterOutput[0], aOutput[0], preFilterOutput[0]);\n      add(preFilterOutput[1], aOutput[1], preFilterOutput[1]);\n    } else {\n      add(filterBypassOutput[0], aOutput[0], filterBypassOutput[0]);\n      add(filterBypassOutput[1], aOutput[1], filterBypassOutput[1]);\n    }\n\n    if (s.modulationMode === 'Off') {\n      if (s.filterB) {\n        add(preFilterOutput[0], bOutput[0], preFilterOutput[0]);\n        add(preFilterOutput[1], bOutput[1], preFilterOutput[1]);\n      } else {\n        add(filterBypassOutput[0], bOutput[0], filterBypassOutput[0]);\n        add(filterBypassOutput[1], bOutput[1], filterBypassOutput[1]);\n      }\n    }\n\n    if (s.filterSub) {\n      add(preFilterOutput[0], multipliedSubOutput, preFilterOutput[0]);\n      add(preFilterOutput[1], multipliedSubOutput, preFilterOutput[1]);\n    } else {\n      add(filterBypassOutput[0], multipliedSubOutput, filterBypassOutput[0]);\n      add(filterBypassOutput[1], multipliedSubOutput, filterBypassOutput[1]);\n    }\n\n    const filterOutput = useStereoSignal();\n\n    filterOutput[0].set(leftFilter(\n      preFilterOutput[0],\n      FILTER_TYPES[s.filterType],\n      m.filterCutoff,\n      m.filterResonance,\n      useConstantBuffer(0)\n    ));\n\n    filterOutput[1].set(rightFilter(\n      preFilterOutput[1],\n      FILTER_TYPES[s.filterType],\n      m.filterCutoff,\n      m.filterResonance,\n      useConstantBuffer(0)\n    ));\n\n    const voiceOutput = useStereoSignal();\n\n    const totalAmplitude = multiply(m.master, modulators.envelope1, useMonoSignal());\n\n    add(filterOutput[0], filterBypassOutput[0], voiceOutput[0]);\n    add(filterOutput[1], filterBypassOutput[1], voiceOutput[1]);\n    multiply(voiceOutput[0], totalAmplitude, voiceOutput[0]);\n    multiply(voiceOutput[1], totalAmplitude, voiceOutput[1]);\n    add(s.output[0], voiceOutput[0], s.output[0]);\n    add(s.output[1], voiceOutput[1], s.output[1]);\n\n    const oscIndex = useVal(0);\n    const lastSubOutput = useVal(subOutput[0]);\n    if (focal) {\n      s.modulatedCutoff[0] = m.filterCutoff[0];\n      s.modulatedResonance[0] = m.filterResonance[0];\n      for (let i = 0; i < voiceOutput[0].length; i ++) {\n        if (i % 2 !== 0) continue;\n        if (oscIndex.current < s.oscilloscope.length) {\n          s.oscilloscope[oscIndex.current] = voiceOutput[0][i] + voiceOutput[1][i];\n          oscIndex.current ++;\n        } else if (subOutput[i] > 0 && lastSubOutput.current < 0) {\n          oscIndex.current = 0;\n        }\n        lastSubOutput.current = subOutput[i];\n      }\n    }\n    exit();\n  }\n}\n\nconst WaveformRenderer = (ctx, settings) => {\n  const w = ctx.canvas.width;\n  const h = ctx.canvas.height;\n\n  ctx.clearRect(0, 0, w, h);\n  if (!settings.enabled) {\n    ctx.fillStyle = gray400;\n    ctx.fillRect(0, 0, w, h);\n    ctx.fillStyle = 'rgba(0,0,0,0.2)';\n    ctx.fillRect(0, h/2, w, h/2);\n\n    ctx.fillStyle = gray200;\n    ctx.font = `${h/5}px Arial`;\n    ctx.fillText(settings.text, h/8, h/4);\n    return;\n  }\n  ctx.strokeStyle = 'white';\n  ctx.lineWidth = 1;\n  ctx.beginPath();\n  ctx.moveTo(-w, h/2);\n  for (let i = 0; i < settings.waveform.length; i ++) {\n    ctx.lineTo(\n      (i/(settings.waveform.length - 1)) * w,\n      (h/2) - (settings.waveform[i] * h/2)\n    );\n  }\n  ctx.lineTo(2 * w, h/2);\n  ctx.stroke();\n  ctx.closePath();\n\n  ctx.save();\n  ctx.clip();\n  ctx.fillStyle = 'rgba(255,255,255,0.1)';\n  ctx.fillRect(0, 0, w, h/2);\n  ctx.fillStyle = 'rgba(0,0,0,0.1)';\n  ctx.fillRect(0, h/2, w, h/2);\n  ctx.restore();\n\n  ctx.fillStyle = gray200;\n  ctx.font = `${h/5}px Arial`;\n  ctx.fillText(settings.text, h/8, h/4);\n}\n\nconst OscillatorAWaveformRenderer = (ctx, settings) => WaveformRenderer(ctx, { ...settings, waveform: settings.waveformA, enabled: settings.oscAEnabled, text: 'A' });\nconst OscillatorBWaveformRenderer = (ctx, settings) => WaveformRenderer(ctx, { ...settings, waveform: settings.waveformB, enabled: settings.oscBEnabled, text: 'B' });\n\nconst FilterRenderer = (ctx, settings) => {\n  const w = ctx.canvas.width;\n  const h = ctx.canvas.height;\n  const transferFunction = getBiQuadFilterTransferFunction(\n    sampleRate,\n    FILTER_TYPES[settings.filterType],\n    settings.modulatedCutoff,\n    settings.modulatedResonance,\n    0\n  );\n  const range = 36;\n\n  ctx.clearRect(0, 0, w, h);\n  ctx.save();\n  roundedRect(ctx, 0, 0, w, h, 4);\n  ctx.clip();\n  ctx.beginPath();\n  ctx.moveTo(-w, h);\n  ctx.lineTo(-w, h/2);\n  ctx.strokeStyle = 'white';\n\n  for (let i = 0; i < transferFunction.length; i ++) {\n    const gain = transferFunction[i][1]\n    ctx.lineTo(\n      (i / (transferFunction.length - 1)) * w,\n      h - (((gain + range) / (2 * range)) * h)\n    )\n  }\n  ctx.lineTo(w * 2, h);\n  ctx.stroke();\n  ctx.closePath();\n\n  ctx.save();\n  ctx.clip();\n  ctx.fillStyle = 'rgba(255,255,255,0.1)';\n  ctx.fillRect(0, 0, w, h/2);\n  ctx.restore();\n\n  ctx.restore();\n}\n\nconst Oscilloscope = (ctx, settings) => {\n  const w = ctx.canvas.width;\n  const h = ctx.canvas.height;\n\n  ctx.clearRect(0, 0, w, h);\n\n  ctx.strokeStyle = '#fff';\n  ctx.beginPath();\n  ctx.moveTo(-w, h/2);\n  for (let i = 0; i < settings.oscilloscope.length; i ++) {\n    ctx.lineTo((i/settings.oscilloscope.length) * w, h/2 + (settings.oscilloscope[i] * h/4));\n  }\n  ctx.lineTo(2 * w, h/2);\n  ctx.stroke();\n  ctx.closePath();\n}\n\nconst oscillatorLayout = (suffix, renderFunction) => box(\n  col([\n    box(\n      canvas(`waveform${suffix}`, renderFunction, 290, 100),\n      'auto',\n      'auto',\n      `${blue500}aa`\n    ),\n    spacer(8),\n    row([\n      box(row([\n        label('Enabled'),\n        spacer(5),\n        box(checkbox(`osc${suffix}Enabled`), 24, 24, gray100, 8),\n      ]), 90, 20),\n      spacer(5),\n      box(\n        wavetablePicker(`wavetable${suffix}`),\n        90,\n        20,\n        blue500,\n        4\n      ),\n      spacer(5),\n      box(selector(`warpMode${suffix}`), 90, 20),\n    ]),\n    spacer(10),\n    row([\n      box(row([\n        box(label('Unison'), 42, 20),\n        box(number(`unison${suffix}`), 43, 20),\n      ]), 90, 20, gray100, 4),\n      spacer(5),\n      box(linear(`wtPos${suffix}`), 90, 20, gray300, 4),\n      spacer(5),\n      box(linear(`warp${suffix}`), 90, 20, gray300, 4),\n    ]),\n    spacer(8),\n    row([\n      spacer(5),\n      col([\n        spacer(15),\n        knob(`phase${suffix}`, { size: 25 })\n      ]),\n      col([\n        knob(`phaseRand${suffix}`, { size: 25 }),\n        spacer(15),\n      ]),\n      spacer(5),\n      col([\n        spacer(15),\n        knob(`spread${suffix}`, { size: 25 })\n      ]),\n      col([\n        knob(`width${suffix}`, { size: 25 }),\n        spacer(15),\n      ]),\n      spacer(5),\n      box(knob(`transpose${suffix}`), 56),\n      spacer(5),\n      box(knob(`pan${suffix}`), 54),\n      box(knob(`level${suffix}`), 54),\n    ]),\n    spacer(5),\n    row([\n      box(\n        label('Θ/Rand'),\n        54,\n        20,\n        gray100,\n        4\n      ),\n      spacer(5),\n      box(\n        label('Det/Wid'),\n        54,\n        20,\n        gray100,\n        4\n      ),\n      spacer(5),\n      box(\n        box(label('Transp'), 56),\n        56,\n        20,\n        gray100,\n        4\n      ),\n      spacer(5),\n      box(\n        row([\n          box(label('Pan'), 54),\n          box(label('Level'), 54),\n        ]),\n        54 * 2,\n        20,\n        gray100,\n        4\n      )\n    ]),\n    spacer(5),\n  ]),\n  'auto',\n  'auto',\n  gray200,\n  8\n);\n\nconst filterLayout = () => box(\n  col([\n    box(\n      canvas('filter', FilterRenderer, 122, 50),\n      'auto',\n      'auto',\n      `${blue500}aa`\n    ),\n    spacer(8),\n    box(selector('filterType'), 120, 20),\n    spacer(8),\n    row([\n      label('A'),\n      spacer(3),\n      box(checkbox('filterA'), 24, 24, gray100, 8),\n      spacer(3),\n      label('B'),\n      spacer(3),\n      box(checkbox('filterB'), 24, 24, gray100, 8),\n      spacer(3),\n      label('S'),\n      spacer(3),\n      box(checkbox('filterSub'), 24, 24, gray100, 8),\n    ]),\n    spacer(6),\n    row([\n      box(knob(`filterCutoff`), 56),\n      box(knob(`filterResonance`), 56),\n    ]),\n    spacer(5),\n    box(\n      row([\n        box(label('Freq'), 56),\n        box(label('Res'), 56)\n      ]),\n      'auto',\n      20,\n      gray100,\n      4\n    ),\n    spacer(5)\n  ]),\n  'auto',\n  'auto',\n  gray200,\n  8\n);\n\nconst modulationLayout = () => box(\n  row([\n    spacer(5),\n    col([\n      spacer(3),\n      box(selector('modulationMode'), 72, 20),\n      spacer(3),\n      box(label('Mod B → A'), 65, 20, gray100, 4),\n      spacer(3),\n    ]),\n    spacer(5),\n    conditional('modulationMode', {\n      'Off': box(spacer(35), 35, 35, gray300, 17.5),\n      _other: knob('modulationAmount', { size: 35 })\n    }),\n    spacer(5),\n  ]),\n  'auto',\n  'auto',\n  gray200,\n  8\n);\n\nconst subLayout = () => box(\n  col([\n    spacer(5),\n    box(row([\n      label('Sub Enabled'),\n      spacer(5),\n      box(checkbox(`subEnabled`), 24, 24, gray100, 8),\n    ]), 'auto', 33),\n    spacer(5),\n    box(selector('subWaveform'), 2 * 56, 20),\n    spacer(5),\n    row([\n      box(knob(`subLevel`), 56),\n      box(knob(`transposeSub`), 56),\n    ]),\n    spacer(5),\n    box(\n      row([\n        box(label('Level'), 56),\n        box(label('Transp'), 56)\n      ]),\n      'auto',\n      20,\n      gray100,\n      4\n    ),\n    spacer(5)\n  ]),\n  155,\n  'auto',\n  gray200,\n  8\n);\n\nconst envelopeLayout = (suffix) => box(\n  col([\n    spacer(5),\n    row([\n      box(selector(`env${suffix}Curve`), 56 * 2, 20),\n    ]),\n    spacer(5),\n    row([\n      box(knob(`attack${suffix}`), 56),\n      box(knob(`decay${suffix}`), 56),\n      box(knob(`sustain${suffix}`), 56),\n      box(knob(`release${suffix}`), 56),\n      box(knob(`envAmplitude${suffix}`), 56),\n    ]),\n    spacer(5),\n    row([\n      box(\n        row([\n          box(label('Attack'), 56),\n          box(label('Decay'), 56),\n          box(label('Sustain'), 56),\n          box(label('Release'), 56),\n          box(label('Level'), 56)\n        ]),\n        'auto',\n        20,\n        gray100,\n        4\n      ),\n    ]),\n    spacer(5),\n  ]),\n  'auto',\n  'auto',\n  gray200,\n  8\n);\n\nconst lfoLayout = (suffix) => box(\n  col([\n    spacer(5),\n    row([\n      box(selector(`sync${suffix}`, { options: [ 'Sync', 'Free' ] }), 56 * 2, 20),\n      box(selector(`waveform${suffix}`), 56 * 2, 20),\n    ]),\n    spacer(5),\n    row([\n      box(\n        conditional(\n          `sync${suffix}`,\n          {\n            Sync: knob(`frequency${suffix}`, { options: LFO_BEAT_FRACTIONS }),\n            Free: knob(`frequency${suffix}`)\n          }\n        ),\n        56\n      ),\n      box(knob(`phase${suffix}`), 56),\n      box(knob(`lfoAmplitude${suffix}`), 56),\n    ]),\n    spacer(5),\n    row([\n      box(\n        row([\n          box(\n            conditional(`sync${suffix}`, { Sync: label('Beats'), Free: label('Freq') }),\n            56\n          ),\n          box(label('Phase'), 56),\n          box(label('Level'), 56),\n        ]),\n        'auto',\n        20,\n        gray100,\n        4\n      ),\n    ]),\n    spacer(5),\n  ]),\n  'auto',\n  'auto',\n);\n\nconst globalsLayout = () => box(\n  row([\n    spacer(5),\n    col([\n      spacer(14),\n      row([\n        box(box(checkbox('retrigger'), 24, 24, gray100, 8), 56),\n        spacer(3),\n        box(number('voiceCount'), 54, 20, gray300, 4),\n      ]),\n      spacer(10),\n      box(\n        row([\n          box(label('Retrig'), 56),\n          box(label('Voices'), 56),\n        ]),\n        'auto',\n        20,\n        gray100,\n        4\n      ),\n      spacer(5),\n    ]),\n    spacer(5),\n    box(\n      canvas(\n        'oscilloscope',\n        Oscilloscope,\n        158,\n        64\n      ),\n      'auto',\n      'auto',\n      gray100,\n      4\n    ),\n    spacer(5),\n    col([\n      spacer(5),\n      row([\n        box(modulationAssigner('modWheel'), 56),\n        spacer(5),\n        box(knob('portamento'), 56),\n        spacer(5),\n        box(row([\n          col([\n            spacer(15),\n            knob('minPitch', { size: 25 })\n          ]),\n          col([\n            knob('maxPitch', { size: 25 }),\n            spacer(15)\n          ]),\n        ]), 56),\n        box(modulationAssigner('pitch'), 56),\n        spacer(5),\n        box(knob('velocityCurve'), 56),\n        box(modulationAssigner('velocity'), 56),\n        spacer(5),\n        box(knob('master'), 56)\n      ]),\n      spacer(4),\n      row([\n        box(label('Mod'), 56, 20, gray100, 4),\n        spacer(5),\n        box(label('Porta'), 56, 20, gray100, 4),\n        spacer(5),\n        box(\n          row([\n            box(label('Range'), 56, 20),\n            box(label('Pitch'), 56, 20),\n          ]),\n          'auto',\n          'auto',\n          gray100,\n          4\n        ),\n        spacer(5),\n        box(\n          row([\n            box(label('V Curve'), 56, 20),\n            box(label('Velocity'), 56, 20),\n          ]),\n          'auto',\n          'auto',\n          gray100,\n          4\n        ),\n        spacer(5),\n        box(label('Master'), 56, 20, gray100, 4),\n      ]),\n      spacer(5),\n    ]),\n    spacer(5),\n  ]),\n  'auto',\n  'auto',\n  gray200,\n  8\n);\nconst lfoTabLayout = (suffix) => {\n  const tabLayout = (color) => box(\n    constantSetter('focusedLFO', `LFO ${suffix}`, col([\n      spacer(4),\n      row([\n        spacer(7),\n        box(label(`LFO${suffix}`), 30, 20),\n        spacer(5),\n        modulationAssigner(`lfo${suffix}`, { size: 30 }),\n        spacer(6)\n      ]),\n      spacer(4),\n    ])),\n    'auto',\n    'auto',\n    color,\n    0\n  );\n  return conditional('focusedLFO', { [`LFO ${suffix}`]: tabLayout(gray200), _other: tabLayout(gray125) })\n}\n\nconst envTabLayout = (suffix) => {\n  const tabLayout = (color) => box(\n    constantSetter('focusedEnvelope', `Env ${suffix}`, col([\n      spacer(4),\n      row([\n        spacer(7),\n        box(label(`Env${suffix}`), 30, 20),\n        spacer(5),\n        modulationAssigner(`envelope${suffix}`, { size: 30 }),\n        spacer(6)\n      ]),\n      spacer(4),\n    ])),\n    'auto',\n    'auto',\n    color,\n    0\n  );\n  return conditional('focusedEnvelope', { [`Env ${suffix}`]: tabLayout(gray200), _other: tabLayout(gray125) })\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: Wavetable,\n  inputs: {\n    input: midi('In'),\n\n    portamento: numberSetting('Portamento', { defaultValue: 0, min: 0, max: 1 }),\n    minPitch: numberSetting('Minimum Pitch', { defaultValue: 0, min: 0, max: 127 }),\n    maxPitch: numberSetting('Maximum Pitch', { defaultValue: 127, min: 0, max: 127 }),\n    velocityCurve: numberSetting('Velocity Curve', { defaultValue: 0, min: -1, max: 1 }),\n\n    spreadA: numberSetting('Oscillator A Spread', { defaultValue: 0, min: 0, max: 2 }),\n    widthA: numberSetting('Oscillator A Width', { defaultValue: 1, min: 0, max: 1 }),\n    wtPosA: numberSetting('Oscillator A WT Pos', { defaultValue: 0, min: 0, max: 1 }),\n    warpA: numberSetting('Oscillator A Warp', { defaultValue: 0, min: -1, max: 1 }),\n    transposeA: numberSetting('Oscillator A Transpose', { defaultValue: 0, min: -12, max: 12 }),\n    phaseA: numberSetting('Oscillator A Base Phase', { defaultValue: 0, min: -Math.PI, max: Math.PI }),\n    phaseRandA: numberSetting('Oscillator A Random Phase', { defaultValue: Math.PI, min: 0, max: Math.PI }),\n    panA: numberSetting('Oscillator A Pan', { defaultValue: 0, max: 1, min: -1 }),\n    levelA: dbSetting('Oscillator A Level', { defaultValue: -6, max: 0 }),\n\n    modulationAmount: numberSetting('Modulation Amount', { defaultValue: 0, max: 1, min: 0 }),\n\n    spreadB: numberSetting('Oscillator B Spread', { defaultValue: 0, min: 0, max: 2 }),\n    widthB: numberSetting('Oscillator B Width', { defaultValue: 1, min: 0, max: 1 }),\n    wtPosB: numberSetting('Oscillator B WT Pos', { defaultValue: 0, min: 0, max: 1 }),\n    warpB: numberSetting('Oscillator B Warp', { defaultValue: 0, min: -1, max: 1 }),\n    transposeB: numberSetting('Oscillator B Transpose', { defaultValue: 0, min: -12, max: 12 }),\n    phaseB: numberSetting('Oscillator B Base Phase', { defaultValue: 0, min: -Math.PI, max: Math.PI }),\n    phaseRandB: numberSetting('Oscillator B Random Phase', { defaultValue: Math.PI, min: 0, max: Math.PI }),\n    panB: numberSetting('Oscillator B Pan', { defaultValue: 0, max: 1, min: -1 }),\n    levelB: dbSetting('Oscillator B Level', { defaultValue: -6, max: 0 }),\n\n    subLevel: dbSetting('Sub Level', { defaultValue: -6, max: 0 }),\n    transposeSub: numberSetting('Sub Transposition', { defaultValue: -12, max: 0, min: -24 }),\n\n    filterCutoff: frequencySetting('Filter Cutoff Frequency', 20000),\n    filterResonance: numberSetting('Filter Resonance', { defaultValue: Math.sqrt(.5), min: .1, max: 18, curve: Curve.Log2 }),\n\n    attack1: adsrTimeSetting('Envelope 1 Attack', 1),\n    decay1: adsrTimeSetting('Envelope 1 Decay', 300),\n    sustain1: zeroOneSetting('Envelope 1 Sustain', 1),\n    release1: adsrTimeSetting('Envelope 1 Release', 100),\n    envAmplitude1: zeroOneSetting('Envelope 1 Level', 1),\n\n    attack2: adsrTimeSetting('Envelope 2 Attack', 1),\n    decay2: adsrTimeSetting('Envelope 2 Decay', 300),\n    sustain2: zeroOneSetting('Envelope 2 Sustain', 0),\n    release2: adsrTimeSetting('Envelope 2 Release', 100),\n    envAmplitude2: zeroOneSetting('Envelope 2 Level', 1),\n\n    attack3: adsrTimeSetting('Envelope 3 Attack', 1),\n    decay3: adsrTimeSetting('Envelope 3 Decay', 300),\n    sustain3: zeroOneSetting('Envelope 3 Sustain', 0),\n    release3: adsrTimeSetting('Envelope 3 Release', 100),\n    envAmplitude3: zeroOneSetting('Envelope 3 Level', 1),\n\n    attack4: adsrTimeSetting('Envelope 4 Attack', 1),\n    decay4: adsrTimeSetting('Envelope 4 Decay', 300),\n    sustain4: zeroOneSetting('Envelope 4 Sustain', 0),\n    release4: adsrTimeSetting('Envelope 4 Release', 100),\n    envAmplitude4: zeroOneSetting('Envelope 4 Level', 1),\n\n    frequency1: numberSetting('LFO 1 Frequency', { defaultValue: 1, min: 0.001, max: 100, curve: Curve.Log2 }),\n    phase1: numberSetting('Phase 1', { defaultValue: 0, min: -Math.PI, max: Math.PI }),\n    lfoAmplitude1: zeroOneSetting('LFO Level 1', 0.5),\n\n    frequency2: numberSetting('LFO 2 Frequency', { defaultValue: 1, min: 0.001, max: 100, curve: Curve.Log2 }),\n    phase2: numberSetting('Phase 2', { defaultValue: 0, min: -Math.PI, max: Math.PI }),\n    lfoAmplitude2: zeroOneSetting('LFO Level 2', 0.5),\n\n    frequency3: numberSetting('LFO 3 Frequency', { defaultValue: 1, min: 0.001, max: 100, curve: Curve.Log2 }),\n    phase3: numberSetting('Phase 3', { defaultValue: 0, min: -Math.PI, max: Math.PI }),\n    lfoAmplitude3: zeroOneSetting('LFO Level 3', 0.5),\n\n    master: dbSetting('Master', { defaultValue: -6, max: 0 }),\n  },\n\n  modulators: {\n    envelope1: modulator('Envelope 1', 0, 1),\n    envelope2: modulator('Envelope 2', 0, 1),\n    envelope3: modulator('Envelope 3', 0, 1),\n    envelope4: modulator('Envelope 4', 0, 1),\n\n    lfo1: modulator('LFO 1', -1, 1),\n    lfo2: modulator('LFO 2', -1, 1),\n    lfo3: modulator('LFO 3', -1, 1),\n\n    pitch: modulator('Pitch', 0, 127),\n    velocity: modulator('Velocity', 0, 1),\n    modWheel: modulator('Mod Wheel', 0, 1),\n  },\n\n  constants: {\n    oscAEnabled: booleanConstant('Oscillator A Enabled', true),\n    oscBEnabled: booleanConstant('Oscillator B Enabled', false),\n    subEnabled: booleanConstant('Sub Enabled', false),\n\n    warpModeA: enumConstant('Oscillator A Warp Mode', Object.keys(WARP_MODES)),\n    warpModeB: enumConstant('Oscillator B Warp Mode', Object.keys(WARP_MODES)),\n    modulationMode: enumConstant('Modulation Mode', MODULATION_MODES),\n\n    wavetableA: wavetableConstant('Oscillator A Wavetable'),\n    wavetableB: wavetableConstant('Oscillator B Wavetable'),\n\n    unisonA: numberConstant('Oscillator A Unison Count', { defaultValue: 1, min: 1, max: 8 }),\n    unisonB: numberConstant('Oscillator B Unison Count', { defaultValue: 1, min: 1, max: 8 }),\n\n    filterType: enumConstant('Filter Type', Object.keys(FILTER_TYPES)),\n    subWaveform: enumConstant('Sub Waveform', Object.keys(SUB_WAVEFORMS)),\n\n    filterA: booleanConstant('Oscillator A to Filter', true),\n    filterB: booleanConstant('Oscillator B to Filter', true),\n    filterSub: booleanConstant('Sub to Filter', true),\n\n    focusedLFO: enumConstant('Focused LFO', ['LFO 1', 'LFO 2', 'LFO 3'], 'LFO 1'),\n    focusedEnvelope: enumConstant('Focused Envelope', ['Env 1', 'Env 2', 'Env 3', 'Env 4'], 'Env 1'),\n\n    env1Curve: enumConstant('Envelope 1 Curve', ['Linear', 'Exponential']),\n    env2Curve: enumConstant('Envelope 2 Curve', ['Linear', 'Exponential']),\n    env3Curve: enumConstant('Envelope 3 Curve', ['Linear', 'Exponential']),\n    env4Curve: enumConstant('Envelope 4 Curve', ['Linear', 'Exponential']),\n\n    waveform1: enumConstant('LFO 1 Waveform', Object.keys(SUB_WAVEFORMS), 'Sine'),\n    waveform2: enumConstant('LFO 2 Waveform', Object.keys(SUB_WAVEFORMS), 'Sine'),\n    waveform3: enumConstant('LFO 3 Waveform', Object.keys(SUB_WAVEFORMS), 'Sine'),\n\n    sync1: enumConstant('LFO 1 Tempo Sync', ['Sync', 'Free']),\n    sync2: enumConstant('LFO 2 Tempo Sync', ['Sync', 'Free']),\n    sync3: enumConstant('LFO 3 Tempo Sync', ['Sync', 'Free']),\n\n    retrigger: booleanConstant('Retrigger', true),\n    voiceCount: numberConstant('Voice Count', { defaultValue: 4, min: 1, max: 16 })\n  },\n\n  outputs: {\n    output: stereo('Out')\n  },\n\n  sharedArrays: {\n    waveformA: 1024,\n    waveformB: 1024,\n    modulatedCutoff: 1,\n    modulatedResonance: 1,\n    oscilloscope: 512,\n  },\n\n  presetTags: {\n    'Instrument': [\n      'Bass',\n      'Synth Lead',\n      'Synth Pad',\n      'Piano/Keys',\n      'Percussion',\n      'Plucked Strings',\n      'Bowed Strings',\n      'Brass',\n      'Woodwinds',\n      'Sound Effect',\n    ],\n    'Genre': [\n      'Ambient',\n      'Chill',\n      'Dubstep',\n      'EDM',\n      'Funk',\n      'Hip Hop',\n      'House',\n      'Jazz',\n      'Lo-Fi',\n      'Soundtrack',\n      'Metal',\n      'Pop',\n      'R&B',\n      'Rock',\n      'Techno',\n      'Trance',\n    ],\n    'Playback': [\n      'Monophonic',\n      'Polyphonic',\n      'Portamento',\n      'Arpeggiated',\n      'Long/Evolving',\n      'Chord',\n      'Slow Attack',\n      'Long Release'\n    ]\n  },\n\n  layout: col([\n    spacer(5),\n    row([\n      spacer(5),\n      oscillatorLayout('A', OscillatorAWaveformRenderer),\n      spacer(1),\n      col([\n        modulationLayout(),\n        spacer(1),\n        filterLayout(),\n      ]),\n      spacer(1),\n      oscillatorLayout('B', OscillatorBWaveformRenderer),\n      spacer(5),\n    ]),\n    spacer(1),\n    row([\n      spacer(5),\n      box(\n        col([\n          row([\n            lfoTabLayout('1'),\n            lfoTabLayout('2'),\n            lfoTabLayout('3'),\n          ]),\n          conditional('focusedLFO', {\n            'LFO 1': lfoLayout('1'),\n            'LFO 2': lfoLayout('2'),\n            'LFO 3': lfoLayout('3'),\n          }),\n        ]),\n        'auto',\n        'auto',\n        gray200,\n        8\n      ),\n      spacer(1),\n      subLayout(),\n      spacer(1),\n      box(\n        col([\n          row([\n            envTabLayout('1'),\n            envTabLayout('2'),\n            envTabLayout('3'),\n            envTabLayout('4'),\n          ]),\n          conditional('focusedEnvelope', {\n            'Env 1': envelopeLayout('1'),\n            'Env 2': envelopeLayout('2'),\n            'Env 3': envelopeLayout('3'),\n            'Env 4': envelopeLayout('4'),\n          }),\n        ]),\n        'auto',\n        'auto',\n        gray200,\n        8\n      ),\n      spacer(5),\n    ]),\n    spacer(1),\n    globalsLayout(),\n    spacer(5)\n  ])\n});\n	1	2023-03-24 13:10:59.476638	Wavetable.wavtool
37	Drum & Drummer	1	const DrumVoice = ({\n  wavetable,\n  trigger,\n  gate,\n  velocity,\n  output,\n  basePitch,\n  modPitch,\n  wtTime,\n  pitchTime,\n  gainTime,\n  filterTime\n}) => {\n  const filterEnvelope = useEnvelope(Curve.Log2, false);\n  const gainEnvelope = useEnvelope(Curve.Log2, false);\n  const pitchEnvelope = useEnvelope(Curve.Log2, false);\n  const wtPosEnvelope = useEnvelope(Curve.Linear, false);\n\n  const oscillator = useWavetableReader(wavetable, WavetableWarpMode.Off);\n\n  const wtPos = wtPosEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(0),\n    wtTime,\n    useConstantBuffer(0),\n    wtTime,\n  );\n\n  subtract(useConstantBuffer(1), wtPos, wtPos);\n\n  const pitch = pitchEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(0),\n    pitchTime,\n    useConstantBuffer(0),\n    pitchTime,\n  );\n\n  const frequency = useMonoSignal();\n  for (let i = 0; i < pitch.length; i ++) {\n    frequency[i] = pitchToHz((pitch[i] * modPitch[i]) + basePitch[i]);\n  }\n  const gain = gainEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(1 * MS_TO_SAMPLES),\n    gainTime,\n    useConstantBuffer(0),\n    gainTime\n  );\n\n  const oscillatorOutput = oscillator(frequency, wtPos, useConstantBuffer(0), useConstantBuffer(0), trigger);\n\n  const filterFrequency = filterEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(0),\n    filterTime,\n    useConstantBuffer(0),\n    filterTime\n  );\n\n  for (let i = 0; i < filterFrequency.length; i ++) {\n    filterFrequency[i] = zeroOneToHz((filterFrequency[i] * 0.75) + 0.25);\n  }\n\n  const voiceOutput = useMonoSignal();\n\n  const filter = useBiQuadFilter();\n\n  const filterOutput = filter(oscillatorOutput, BiQuadFilterMode.LowPass, filterFrequency, useConstantBuffer(0.2), useConstantBuffer(0));\n\n  multiply(filterOutput, gain, voiceOutput);\n\n  add(output[0], voiceOutput, output[0]);\n  add(output[1], voiceOutput, output[1]);\n}\n\nconst RattleVoice = (trigger, gate, velocity, output, attackTime, decayTime) => {\n  const gainEnvelope = useEnvelope(Curve.Log2, false);\n\n  const gain = gainEnvelope(\n    trigger,\n    gate,\n    attackTime,\n    decayTime,\n    useConstantBuffer(0),\n    decayTime\n  );\n\n  multiply(gain, gain, gain);\n  multiply(gain, useConstantBuffer(0.35), gain);\n\n  const noise = useOscillator(Waveform.Noise);\n  const voiceOutput = useMonoSignal();\n  const filter = useBiQuadFilter();\n  const shaper = useBiQuadFilter();\n  const filtered = filter(noise(useConstantBuffer(8000)), BiQuadFilterMode.BandPass, useConstantBuffer(3000), useConstantBuffer(.25), useConstantBuffer(0));\n  const shaped = shaper(filtered, BiQuadFilterMode.EQBand, useConstantBuffer(4000), useConstantBuffer(.5), useConstantBuffer(1.5));\n  multiply(shaped, gain, voiceOutput);\n\n  add(output[0], voiceOutput, output[0]);\n  add(output[1], voiceOutput, output[1]);\n}\n\nconst hatToneFrequencies = [953, 950, 1023, 1135, 1258].map((x) => x * 1.3);\n\nconst CymbalVoice = (settings) => {\n  const { wavetable, trigger, gate, velocity, output, attackTime, decayTime } = settings;\n  const zero = useConstantBuffer(0);\n  const gainEnvelope = useEnvelope(Curve.Log2, false);\n  const carrierWtPosEnvelope = useEnvelope(Curve.Linear, false);\n  const modulatorWtPosEnvelope = useEnvelope(Curve.Linear, false);\n\n  const gain = gainEnvelope(\n    trigger,\n    gate,\n    attackTime,\n    decayTime,\n    useConstantBuffer(0),\n    decayTime,\n  );\n  multiply(gain, gain, gain);\n  multiply(gain, useConstantBuffer(0.4), gain);\n\n  const carrierWtPos = carrierWtPosEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(0),\n    useConstantBuffer(decayTime),\n    useConstantBuffer(0),\n    useConstantBuffer(decayTime),\n  );\n  subtract(useConstantBuffer(1), carrierWtPos, carrierWtPos);\n\n  const modulatorWtPos = modulatorWtPosEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(0),\n    useConstantBuffer(decayTime),\n    useConstantBuffer(0),\n    useConstantBuffer(decayTime),\n  )\n  subtract(useConstantBuffer(1), modulatorWtPos, modulatorWtPos);\n\n  const summedTones = useMonoSignal();\n  summedTones.fill(0);\n\n  const osc1 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const osc2 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const osc3 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const osc4 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const osc5 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n\n  const osc1Freq = settings.carrierRoot;\n\n  const osc2Freq = useMonoSignal();\n  const scaledDelta1 = useMonoSignal();\n  multiply(settings.carrier1Delta, settings.carrierScale, scaledDelta1);\n  add(osc1Freq, scaledDelta1, osc2Freq);\n\n  const osc3Freq = useMonoSignal();\n  const scaledDelta2 = useMonoSignal();\n  multiply(settings.carrier2Delta, settings.carrierScale, scaledDelta2);\n  add(osc2Freq, scaledDelta2, osc3Freq);\n\n  const osc4Freq = useMonoSignal();\n  const scaledDelta3 = useMonoSignal();\n  multiply(settings.carrier3Delta, settings.carrierScale, scaledDelta3);\n  add(osc3Freq, scaledDelta3, osc4Freq);\n\n  const osc5Freq = useMonoSignal();\n  const scaledDelta4 = useMonoSignal();\n  multiply(settings.carrier4Delta, settings.carrierScale, scaledDelta4);\n  add(osc4Freq, scaledDelta4, osc5Freq);\n\n  add(summedTones, osc1(osc1Freq, carrierWtPos, zero, zero, trigger), summedTones);\n  add(summedTones, osc2(osc2Freq, carrierWtPos, zero, zero, trigger), summedTones);\n  add(summedTones, osc3(osc3Freq, carrierWtPos, zero, zero, trigger), summedTones);\n  add(summedTones, osc4(osc4Freq, carrierWtPos, zero, zero, trigger), summedTones);\n  add(summedTones, osc5(osc5Freq, carrierWtPos, zero, zero, trigger), summedTones);\n\n  const ringOsc1 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const ringOsc2 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const ring1 = ringOsc1(settings.modulator1, modulatorWtPos, useConstantBuffer(0), useConstantBuffer(0), trigger);\n  const ring2 = ringOsc2(settings.modulator2, modulatorWtPos, useConstantBuffer(0), useConstantBuffer(0), trigger);\n\n  const bandpass1 = useBiQuadFilter();\n  const bandpass2 = useBiQuadFilter();\n  const highpass = useBiQuadFilter();\n\n  const bandpassed1 = bandpass1(summedTones, BiQuadFilterMode.BandPass, settings.band1Freq, settings.band1Res, useConstantBuffer(0));\n  const bandpassed2 = bandpass2(summedTones, BiQuadFilterMode.BandPass, settings.band2Freq, settings.band2Res, useConstantBuffer(0));\n\n  multiply(ring1, bandpassed1, ring1);\n  multiply(ring2, bandpassed2, ring2);\n  add(ring1, ring2, summedTones);\n\n  highpassed = highpass(summedTones, BiQuadFilterMode.HighPass, useConstantBuffer(1000), useConstantBuffer(1), useConstantBuffer(0));\n  multiply(highpassed, gain, highpassed);\n  add(output[0], highpassed, output[0]);\n  add(output[1], highpassed, output[1]);\n}\n\nconst hatPitchTiming = {\n  54: {\n    cymbalDecay: 200 * MS_TO_SAMPLES,\n    cymbalAttack: 0,\n    noiseDecay: 100 * MS_TO_SAMPLES,\n    noiseAttack: 0 * MS_TO_SAMPLES,\n    noiseGain: 1,\n    cymbalGain: 1,\n  },\n  56: {\n    cymbalDecay: 100 * MS_TO_SAMPLES,\n    cymbalAttack: 4 * MS_TO_SAMPLES,\n    noiseDecay: 25 * MS_TO_SAMPLES,\n    noiseAttack: 4 * MS_TO_SAMPLES,\n    noiseGain: 0.25,\n    cymbalGain: 0.5,\n  },\n  58: {\n    cymbalDecay: 1600 * MS_TO_SAMPLES,\n    cymbalAttack: 0,\n    noiseDecay: 150 * MS_TO_SAMPLES,\n    noiseAttack: 0 * MS_TO_SAMPLES,\n    noiseGain: 1,\n    cymbalGain: 1,\n  },\n};\n\nconst DigitalDrums = (settings) => {\n  const { input, output, kick, snare, hat, hiTom, lowTom, midTom, flrTom, kickWavetable, snareWavetable, hatWavetable, tomWavetable } = settings;\n  output[0].fill(0);\n  output[1].fill(0);\n\n  kick[0].fill(0);\n  kick[1].fill(0);\n\n  snare[0].fill(0);\n  snare[1].fill(0);\n\n  hat[0].fill(0);\n  hat[1].fill(0);\n\n  hiTom[0].fill(0);\n  hiTom[1].fill(0);\n\n  lowTom[0].fill(0);\n  lowTom[1].fill(0);\n\n  midTom[0].fill(0);\n  midTom[1].fill(0);\n\n  flrTom[0].fill(0);\n  flrTom[1].fill(0);\n\n\n\n  const kickVoice = useSingleVoice(input, [48], 2);\n  const snareVoice = useSingleVoice(input, [50, 51, 52], 2);\n  const hatVoice = useSingleVoice(input, [54,56,58], 2);\n\n  const hiTomVoice = useSingleVoice(input, [59], 2);\n  const midTomVoice = useSingleVoice(input, [57], 2);\n  const lowTomVoice = useSingleVoice(input, [55], 2);\n  const flrTomVoice = useSingleVoice(input, [53], 2);\n\n  const kickModPitch = useMonoSignal();\n  subtract(settings.kickAttackPitch, settings.kickResonantPitch, kickModPitch);\n\n  const kickPitchTime = useMonoSignal();\n  multiply(kickVoice.velocity, useConstantBuffer(50 * MS_TO_SAMPLES), kickPitchTime);\n  add(kickPitchTime, useConstantBuffer(100 * MS_TO_SAMPLES), kickPitchTime);\n\n  DrumVoice({\n    ...kickVoice,\n    wavetable: kickWavetable,\n    output: kick,\n    basePitch: settings.kickResonantPitch,\n    modPitch: kickModPitch,\n    wtTime: settings.kickWavetableScanTime,\n    pitchTime: settings.kickPitchDropTime,\n    gainTime: settings.kickAmplitudeDecayTime,\n    filterTime: settings.kickFilterDropTime,\n  });\n\n  add(output[0], kick[0], output[0]);\n  add(output[1], kick[1], output[1]);\n\n  const snareSkin1 = useStereoSignal();\n  snareSkin1[0].fill(0);\n  snareSkin1[1].fill(0);\n\n  DrumVoice({\n    ...snareVoice,\n    wavetable: snareWavetable,\n    output: snareSkin1,\n    basePitch: settings.snareSkin1Pitch,\n    modPitch: useConstantBuffer(.6),\n    wtTime: settings.snareSkin1WavetableScanTime,\n    pitchTime: useConstantBuffer(300 * MS_TO_SAMPLES),\n    gainTime: settings.snareSkin1Decay,\n    filterTime: settings.snareSkin1Decay,\n  });\n\n  multiply(snareSkin1[0], settings.snareSkin1Level, snareSkin1[0]);\n  multiply(snareSkin1[1], settings.snareSkin1Level, snareSkin1[1]);\n  add(snare[0], snareSkin1[0], snare[0]);\n  add(snare[1], snareSkin1[1], snare[1]);\n\n  const snareSkin2 = useStereoSignal();\n\n  const ringPitch = useMonoSignal();\n  add(settings.snareSkin2Offset, settings.snareSkin1Pitch, ringPitch);\n  snareSkin2[0].fill(0);\n  snareSkin2[1].fill(0);\n\n  DrumVoice({\n    ...snareVoice,\n    wavetable: snareWavetable,\n    output: snareSkin2,\n    basePitch: ringPitch,\n    modPitch: useConstantBuffer(.75),\n    wtTime: settings.snareSkin2WavetableScanTime,\n    pitchTime: useConstantBuffer(400 * MS_TO_SAMPLES),\n    gainTime: settings.snareSkin2Decay,\n    filterTime: settings.snareSkin2Decay,\n  });\n\n  multiply(snareSkin2[0], settings.snareSkin2Level, snareSkin2[0]);\n  multiply(snareSkin2[1], settings.snareSkin2Level, snareSkin2[1]);\n\n  add(snare[0], snareSkin2[0], snare[0]);\n  add(snare[1], snareSkin2[1], snare[1]);\n\n  const snareSnares = useStereoSignal();\n  snareSnares[0].fill(0);\n  snareSnares[1].fill(0);\n  RattleVoice(snareVoice.trigger, snareVoice.gate, snareVoice.velocity, snareSnares, settings.snareSnaresAttack, settings.snareSnaresDecay);\n\n  multiply(snareSnares[0], settings.snareSnaresLevel, snareSnares[0]);\n  multiply(snareSnares[1], settings.snareSnaresLevel, snareSnares[1]);\n\n  add(snare[0], snareSnares[0], snare[0]);\n  add(snare[1], snareSnares[1], snare[1]);\n\n  add(output[0], snare[0], output[0]);\n  add(output[1], snare[1], output[1]);\n\n  const hatCymbalDecay = useMonoSignal();\n  const hatNoiseDecay = useMonoSignal();\n  const hatNoiseAttack = useMonoSignal();\n  const hatCymbalAttack = useMonoSignal();\n  for (let i = 0; i < hatCymbalDecay.length; i++) {\n    const timingSpec = hatPitchTiming[hatVoice.pitch[i]];\n    hatCymbalDecay[i] = timingSpec?.cymbalDecay || 0;\n    hatNoiseDecay[i] = timingSpec?.noiseDecay || 0;\n    hatNoiseAttack[i] = timingSpec?.noiseAttack || 0;\n    hatCymbalAttack[i] = timingSpec?.cymbalAttack || 0;\n  }\n  const hatCymbal = useStereoSignal();\n  hatCymbal[0].fill(0);\n  hatCymbal[1].fill(0);\n  CymbalVoice({\n    ...hatVoice,\n    wavetable: hatWavetable,\n    output: hatCymbal,\n    attackTime: hatCymbalAttack,\n    decayTime: hatCymbalDecay,\n    carrierRoot: settings.hatCarrierRoot,\n    carrierScale: settings.hatCarrierScale,\n    carrier1Delta: settings.hatCarrierDelta1,\n    carrier2Delta: settings.hatCarrierDelta2,\n    carrier3Delta: settings.hatCarrierDelta3,\n    carrier4Delta: settings.hatCarrierDelta4,\n    modulator1: settings.hatModulator1Freq,\n    modulator2: settings.hatModulator2Freq,\n    band1Freq: settings.hatCarrierBandpass1Freq,\n    band2Freq: settings.hatCarrierBandpass2Freq,\n    band1Res: settings.hatCarrierBandpass1Res,\n    band2Res: settings.hatCarrierBandpass2Res,\n  });\n  const hatNoise = useStereoSignal();\n  hatNoise[0].fill(0);\n  hatNoise[1].fill(0);\n  RattleVoice(hatVoice.trigger, hatVoice.gate, hatVoice.velocity, hatNoise, hatNoiseAttack, hatNoiseDecay);\n  for (let i = 0; i < hatNoise[0].length; i ++) {\n    const timingSpec = hatPitchTiming[hatVoice.pitch[i]];\n    const noiseGain = timingSpec?.noiseGain || 0;\n    const cymbalGain = timingSpec?.cymbalGain || 0;\n    hatNoise[0][i] *= noiseGain;\n    hatNoise[1][i] *= noiseGain;\n    hatCymbal[0][i] *= cymbalGain;\n    hatCymbal[1][i] *= cymbalGain;\n  }\n  add(hat[0], hatNoise[0], hat[0]);\n  add(hat[1], hatNoise[1], hat[1]);\n  add(hat[0], hatCymbal[0], hat[0]);\n  add(hat[1], hatCymbal[1], hat[1]);\n\n  add(output[0], hat[0], output[0]);\n  add(output[1], hat[1], output[1]);\n\n  const tomPitchTime = useMonoSignal();\n  multiply(hiTomVoice.velocity, useConstantBuffer(50 * MS_TO_SAMPLES), tomPitchTime);\n  add(tomPitchTime, useConstantBuffer(100 * MS_TO_SAMPLES), tomPitchTime);\n\n  for (let i = 0; i < 4; i ++) {\n    let voice, currentOutput, resonantPitchBuffer;\n    if (i === 0) {\n      voice = hiTomVoice;\n      currentOutput = hiTom;\n      resonantPitchBuffer = settings.hiTomResonantPitch;\n    } else if (i === 1) {\n      voice = midTomVoice;\n      currentOutput = midTom;\n      resonantPitchBuffer = settings.midTomResonantPitch;\n    } else if (i === 2) {\n      voice = lowTomVoice;\n      currentOutput = lowTom;\n      resonantPitchBuffer = settings.lowTomResonantPitch;\n    } else if (i === 3) {\n      voice = flrTomVoice;\n      currentOutput = flrTom;\n      resonantPitchBuffer = settings.flrTomResonantPitch;\n    }\n\n    DrumVoice({\n      ...voice,\n      wavetable: tomWavetable,\n      output: currentOutput,\n      basePitch: resonantPitchBuffer,\n      modPitch: settings.tomModPitch,\n      wtTime: settings.tomWavetableScanTime,\n      pitchTime: settings.tomPitchDropTime,\n      gainTime: settings.tomAmplitudeDecayTime,\n      filterTime: settings.tomFilterDropTime,\n    });\n\n    add(output[0], currentOutput[0], output[0]);\n    add(output[1], currentOutput[1], output[1]);\n  }\n}\n\nconst WAVETABLES = {\n  Kick: '[encoded data, 11456 characters]',\n  Snare: '[encoded data, 5380 characters]',\n  Crash: 'ZSTDKLUv/WBKCPVJANSRCQCCAIAng4KI7oyXjDCFlHfFZzVbSlZiWj5l+HHdelZ7V3EGXsJFWy9+IlklQzo5XkeJ5rCfy0/Um8vutqydG4Zhc2xlZVqiUAxIGUK8QPpE502pWHFh62R3YbFXGkoUPL0wIyr/KPgsKjWqQLtOwV4TcNGB6pJWom2vLLo6w63LktRy3v/oGvMT+yP/3/2G9iXpkNZBwCCoOJBRepNnVlg2TINC3DqlNQc0VTccQEpN+ltFaL5uHW4VaNNgQV2+YGRrz3lzhg2ME4jkezNsGl/NWehe/m0shE+dOrWLyBfVDNrY1+fPQMQAt+Cp9p27k1GLxoQ/gMx9OX3offN+nX/AfwCAANiBi4FWf6p7BnficbRs/mdhZIliGWN+Ztls8nVHgSqO1ZuHqZu2ncJKzXnW790U49fk2uHo2KHJArV7nW6GPHM+Zh1g1V8pY1xn5mn9aNdjnlpOTnhA+TK0J0kg1h29IH8oxDN8QE5MPlVOWu1bAVxPXaFi0202f3+VSq7LxoPcqO03+cL+Of7W9xzsBNwQyS+1f6LhkqWHQ4FJf5KAf4NMhmmHwIXmgEF56G9mZkxevlgrVjpW8FcJWlFb6VpzWDdUC08dSr1G60UDSJdMlFLEWEVe8mJZZ1psnHIaehiCW4mwjlWRNJHZjjaLUIf+g8KBuYClgA6BboFngd2AAID/HwCA1YEnhLCGO4mqi+KNvI/8kFyRrJDijjmMNYmnhpuFIocJjJGULKCJrdO6KcYLzrfRMNENzULGyr2DtASrkqEymMOONoW3e+VyvWtKZzxmfmgWbWlypXZZeNh2VnK/a11kgF1FWHZVllXlWGlf+mhGdeaDWJQRpmm4jcp22wjqPvVt/Hr/0/5B+4b1A+6J5HjYIMkntuefhIe3bnlXpEOgNC8rUydZKO8scDNKOl1AQEVBSRBNSVEeViJbfF9YYk1jpGJSYcJgb2KTZ8Vwwn09jQOdXKq9spC0sq+NpaOYz4trgc16L3jaeJ97Nn+NgvCEGIYghl+FPoQPg/qB+T8AgACEKYbahv6G2oenikWQ9JgppKGwlrwtxvLLVs3WytzFUMAAvCa6L7vSvj7EacpS0B7VLtgm2e3XiNTuzgTHhbxKr32fto0qe3dpQVrCTo9HcESDRIVGEkn0SmhLWUp0SApHwkcbTPFUMmLfcliFy5ekqN+2GcKByq3QadV52VLd/eD441zlH+Rt3+/W5MoVvKCrsJpOijZ7xG3yYWlXuU2ARLU7zDOeLT8qqSpyL5E4TkVZVOhjA3LafAuD8IPIf7R3fm0uY7BaZlX4Uz9WcltCYjZp826DcoRzLHJBb91rMmlLaMZpuW2ic5R6aoEgh/6KvIyGjM2KLYg9hW6CXwCAkoA9gtSEWoj6jPKSX5oeo7asbLZlv9PGDMykzl7OP8uExa69f7T1qhWi55oullWUWJW2mHudZaL/pfmmbqQknqaUHYkIffxxVWkVZKxiB2Wlaqdy9XtlheiNnZTzmLGa65n/ln+SJI3AhxuD+H/vfmaAloR3i8CU3Z/6qwG4zcJLy7PQrNJZ0TXN88ZLv9O26a24pE6bqZHDh7B9mHPHaaFgnFg4UuhNBUy7TAdQo1UfXeZlb29UeW+D0Y2gmO6jdq+aunbECsx90DzRJs6ax2m+wbP8qFuft5ddkhWPQ40qjDaLGYrViJuHq4Y8hleG6Yaxh1mIhYjjh0OGmIN/AIBpfp985nqHebp4mngleTN6i3vyfDR+QH8hgAyBRYIEhF6GLokfjM+O4pArkrKSoZIsknORdpAWjyKNb4rfhnyCgH1SeIRzwG+lbaVt5m9DdFZ6jYFHifGQG5iFniWkIanFrX+yxLcEvoLFRs4Q2E/iK+y59Cb73P6i/5r9Lvnw8nbrQ+PC2kPS/skNwm66+LJiq06jYZpgkEmFZ3k7bXph7VZKTiJIz0RvRNpGr0tpUnVaXWO9bGB2GoC/iQ6TqpsloxCpMK1/rxywQa8crdmplqWJoACbc5VlkFqMpIlUiD6I+Yj9ibuKwYrLidSHGoUKgih/7Hyne3l7THzmfZ8AgBmBwYGqgaWAqn7le7F4h3XhciFxdnDhcCty/3MCduF3bHmqesd7H30Rf+aBvIV1irSP75R1maSc1Z2DnGmYfZEGiI980W+zYj9WfktpQ8c+Kj7BQVlJaFQPYktxG4GkkFSf2awfuTzEY87D137goegN8H32ld3/NP6O+cHx1eYC2ajIbbYpo9uPl31abfJf2lU6T+5LtUs0TiFTQFpgY0ZusnpIiJyWKaVrs9fA8sxZ19bfVeb46vPtme848Pjv5e4U7YrqKufP4nbdIdcB0GzIuMBJuXOybaxGp+SiCZ9om7aXz5Osj3SLaYfPg+iA136jfTd9aX0Gful+vwCAh4PvhR6HCYewhTSDx3+3e1p3+nLHbtNqFWdsY8dfF1xsWPdU/1HVT9JOMk8VUWNU6FhPXkhkkGoGcYx3FH6IhMSKqJAOlt2a/J55onClIqjoqiauPrJzt9a9SMV+zQfWXd795WzsRvFG9ET1PvRU8b3syubN3xTY3c9Tx4a+bLXmq8Sh1Jbxigh+KXCNYYxSnEM8Ne0nHBweEjYKiAQlARMAUgHeBLwK8BKCHXIqqjn6Sg5eYHI8h8WbEa9CwKbOuNkx4S7lFuZ25Ong/dsl1qrPrsgywTS5wbD/pzSft5bljhOIgYJOfnh75XlheaZ5b3p+e598q32Nfjp/tH/fAIBef05+3HwRe/h4mHYFdFlxr24pbOZpAmiEZnhlzmR+ZHFkkmTZZDtltmVLZutmiWcHaE9oSWjxZ19nuGZBZlhmTmd/aTFthnKAefCBiIvtlbqggavgtYm/M8inz7zVXtqI3UHfmt+u3q/c0tlH1jnSt821yArDirwHtWissqILmLyMFoGDdWhqGWDUVr1O3EcbQl09gjlyNhs0dzKLMWExCjKeMzo2BDocP6NFuE1sV8Jiq2/2fVGNS51grQi9wssG2VDkVu3t8/z3efls+PH0K+9I55LdYNIbxj25Say+nwqUfolbgMR4yXJrbqFrWGpyasdrI25JcfN003ibfP+7DduDbRJtuzfhPIqX4sudsUgKKJBDuwOQ2+8BAVPANCz4GfAUMAVsCpB04GCc7T8E',\n\n   'Tom': '[encoded data, 5528 characters]'\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  noteInputHint: {\n    interface: NoteInterfaceType.Pads,\n    usedPitches: {\n      48: 'Kick',\n      50: 'Snare L',\n      51: 'Snare Ghost',\n      52: 'Snare R',\n      54: 'Hat Closed',\n      56: 'Hat Pedal',\n      58: 'Hat Open',\n    }\n  },\n  implementation: DigitalDrums,\n  constants: {\n    kickWavetable: wavetableConstant('Kick Wavetable', WAVETABLES.Kick),\n    snareWavetable: wavetableConstant('Snare Wavetable', WAVETABLES.Snare),\n    hatWavetable: wavetableConstant('Hat Wavetable', WAVETABLES.Crash),\n    tomWavetable: wavetableConstant('Tom Wavetable', WAVETABLES.Tom)\n  },\n  inputs: {\n    input: midi('In'),\n\n    kickResonantPitch: numberSetting('Kick Resonant Pitch', { min: 10, max: 30, defaultValue: 22 }),\n    kickAttackPitch: numberSetting('Kick Attack Pitch', { min: 40, max: 55, defaultValue: 43.5 }),\n    kickWavetableScanTime: adsrTimeSetting('Kick Wavetable Scan Time', 220),\n    kickPitchDropTime: adsrTimeSetting('Kick Pitch Drop Time', 150),\n    kickAmplitudeDecayTime: adsrTimeSetting('Kick Amplitude Decay Time', 1400),\n    kickFilterDropTime: adsrTimeSetting('Kick Filter Drop Time', 92),\n\n    snareSkin1Pitch: numberSetting('Snare Skin 1 Pitch', { min: 30, max: 60, defaultValue: 42.8 }),\n    snareSkin1Level: dbSetting('Snare Skin 1 Level', { defaultValue: -4, max: 0, min: -24 }),\n    snareSkin1WavetableScanTime: adsrTimeSetting('Snare Skin 1 Wavetable Scan Time', 30),\n    snareSkin1Decay: adsrTimeSetting('Snare Skin 1 Decay', 230),\n\n    snareSkin2Offset: numberSetting('Snare Skin 2 Offset', { min: 5, max: 9, defaultValue: 7.48 }),\n    snareSkin2Level: dbSetting('Snare Skin 2 Level', { defaultValue: -15, max: -3, min: -30 }),\n    snareSkin2WavetableScanTime: adsrTimeSetting('Snare Skin 2 Wavetable Scan Time', 30),\n    snareSkin2Decay: adsrTimeSetting('Snare Skin 2 Decay', 230),\n\n    snareSnaresLevel: dbSetting('Snare Snares Level', { defaultValue: 0 }),\n    snareSnaresAttack: adsrTimeSetting('Snare Snares Attack', 17),\n    snareSnaresDecay: adsrTimeSetting('Snare Snares Decay', 190),\n\n    hatCarrierDelta1: numberSetting('Hat Carrier Delta 1', { min: 0, max: 100, defaultValue: 1.6 }),\n    hatCarrierDelta2: numberSetting('Hat Carrier Delta 2', { min: 0, max: 100, defaultValue: 15.8 }),\n    hatCarrierDelta3: numberSetting('Hat Carrier Delta 3', { min: 0, max: 100, defaultValue: 31.8 }),\n    hatCarrierDelta4: numberSetting('Hat Carrier Delta 4', { min: 0, max: 100, defaultValue: 51.0 }),\n    hatCarrierRoot: numberSetting('Hat Carrier Root', { min: 900, max: 1100, defaultValue: 949 }),\n    hatCarrierScale: numberSetting('Hat Carrier Scale', { min: 1, max: 5, defaultValue: 3 }),\n    hatModulator1Freq: numberSetting('Hat Modulator 1 Freq', { min: 2000, max: 8000, defaultValue: 4000 }),\n    hatModulator2Freq: numberSetting('Hat Modulator 2 Freq', { min: 2000, max: 8000, defaultValue: 5000 }),\n    hatCarrierBandpass1Freq: numberSetting('Hat Carrier BPF 1 Freq', { min: 2000, max: 8000, defaultValue: 5000 }),\n    hatCarrierBandpass2Freq: numberSetting('Hat Carrier BPF 2 Freq', { min: 2000, max: 8000, defaultValue: 6500 }),\n    hatCarrierBandpass1Res: numberSetting('Hat Carrier BPF 1 Res', { min: 0.1, max: 5, defaultValue: 1 }),\n    hatCarrierBandpass2Res: numberSetting('Hat Carrier BPF 2 Res', { min: 0.1, max: 5, defaultValue: 2 }),\n\n    hiTomResonantPitch: numberSetting('High Tom Resonant Pitch', { min: 25, max: 60, defaultValue: 50 }),\n    midTomResonantPitch: numberSetting('Mid Tom Resonant Pitch', { min: 25, max: 60, defaultValue: 46 }),\n    lowTomResonantPitch: numberSetting('Low Tom Resonant Pitch', { min: 25, max: 60, defaultValue: 40 }),\n    flrTomResonantPitch: numberSetting('Floor Tom Resonant Pitch', { min: 25, max: 60, defaultValue: 34 }),\n\n    tomModPitch: numberSetting('Tom Modulated Pitch', { min: 0, max: 12, defaultValue: 6 }),\n    tomWavetableScanTime: adsrTimeSetting('Tom Wavetable Scan Time', 670),\n    tomPitchDropTime: adsrTimeSetting('Tom Pitch Drop Time', 220),\n    tomAmplitudeDecayTime: adsrTimeSetting('Tom Amplitude Decay Time', 338),\n    tomFilterDropTime: adsrTimeSetting('Tom Filter Drop Time', 110),\n  },\n  outputs: {\n    output: stereo('Out'),\n    kick: collapse(stereo('Kick')),\n    snare: collapse(stereo('Snare')),\n    hat: collapse(stereo('Hat')),\n    hiTom: collapse(stereo('High Tom')),\n    midTom: collapse(stereo('Mid Tom')),\n    lowTom: collapse(stereo('Low Tom')),\n    flrTom: collapse(stereo('Floor Tom')),\n  },\n  layout: row([\n    col([\n      box(\n        col([\n          spacer(5),\n          label('Hat'),\n          spacer(5),\n          row([\n            col([\n              row([spacer(40), label('Carrier')]),\n              spacer(10),\n              row([\n                box(label('Root'), 40),\n                box(linear('hatCarrierRoot'), 132, 20, gray300, 4),\n              ]),\n              spacer(4),\n              row([\n                box(label('Delta'), 40),\n                col([\n                  row([\n                    knob('hatCarrierDelta1', { size: 30 }),\n                    spacer(4),\n                    knob('hatCarrierDelta2', { size: 30 }),\n                    spacer(4),\n                    knob('hatCarrierDelta3', { size: 30 }),\n                    spacer(4),\n                    knob('hatCarrierDelta4', { size: 30 }),\n                  ])\n                ]),\n              ]),\n              spacer(4),\n              row([\n                box(label('Mult'), 40),\n                box(linear('hatCarrierScale'), 132, 20, gray300, 4),\n              ])\n            ]),\n            spacer(4),\n            col([\n              row([label('BPF')]),\n              spacer(4),\n              knob('hatCarrierBandpass1Freq', { size: 40 }),\n              spacer(4),\n              knob('hatCarrierBandpass2Freq', { size: 40 }),\n            ]),\n            spacer(4),\n            col([\n              row([label('Mod')]),\n              spacer(4),\n              knob('hatModulator1Freq', { size: 40 }),\n              spacer(4),\n              knob('hatModulator2Freq', { size: 40 }),\n            ]),\n            spacer(4),\n            col([\n              row([label('Pres.')]),\n              spacer(4),\n              knob('hatCarrierBandpass1Res', { size: 40 }),\n              spacer(4),\n              knob('hatCarrierBandpass2Res', { size: 40 }),\n            ]),\n            spacer(3),\n          ]),\n          spacer(5),\n        ]),\n        310,\n        'auto',\n        gray200,\n        8\n      ),\n      spacer(1),\n      box(\n        col([\n          spacer(5),\n          label('Snare'),\n          row([\n            spacer(5),\n            box(label('Skin 1'), 40, 'auto'),\n            spacer(2),\n            titled.knob('snareSkin1Level', 'Level', 30),\n            box(titled.knob('snareSkin1Pitch', 'Pitch', 30), 60, 'auto'),\n            col([\n              row([\n                box(label('Timbre'), 50, 20),\n                box(linear('snareSkin1WavetableScanTime'), 90, 20, gray300, 4),\n              ]),\n              spacer(2),\n              row([\n                box(label('Mute'), 50, 20),\n                box(linear('snareSkin1Decay'), 90, 20, gray300, 4)\n              ]),\n              spacer(2),\n            ]),\n            spacer(4),\n          ]),\n          row([\n            spacer(5),\n            box(label('Skin 2'), 40, 'auto'),\n            spacer(2),\n            titled.knob('snareSkin2Level', 'Level', 30),\n            box(titled.knob('snareSkin2Offset', 'Shift', 30), 60, 'auto'),\n            col([\n              row([\n                box(label('Timbre'), 50, 20),\n                box(linear('snareSkin2WavetableScanTime'), 90, 20, gray300, 4),\n              ]),\n              spacer(2),\n              row([\n                box(label('Mute'), 50, 20),\n                box(linear('snareSkin2Decay'), 90, 20, gray300, 4)\n              ]),\n              spacer(2),\n            ]),\n            spacer(4),\n          ]),\n          row([\n            spacer(5),\n            box(label('Snares'), 40, 'auto'),\n            spacer(2),\n            titled.knob('snareSnaresLevel', 'Level', 30),\n            box(spacer(0), 60, 'auto'),\n            col([\n              row([\n                box(label('Attack'), 50, 20),\n                box(linear('snareSnaresAttack'), 90, 20, gray300, 4),\n              ]),\n              spacer(2),\n              row([\n                box(label('Decay'), 50, 20),\n                box(linear('snareSnaresDecay'), 90, 20, gray300, 4)\n              ]),\n              spacer(2),\n            ]),\n            spacer(4),\n          ])\n        ]),\n        310,\n        'auto',\n        gray200,\n        8\n      ),\n      spacer(1),\n      box(\n        col([\n          spacer(5),\n          label('Kick'),\n          row([\n            titled.knob('kickResonantPitch', 'Res. Pitch', 60),\n            titled.knob('kickAttackPitch', 'Atk. Pitch', 60),\n            col([\n              row([\n                box(label('Pitch'), 50, 20),\n                box(linear('kickPitchDropTime'), 90, 20, gray300, 4),\n              ]),\n              spacer(2),\n              row([\n                box(label('Timbre'), 50, 20),\n                box(linear('kickWavetableScanTime'), 90, 20, gray300, 4)\n              ]),\n              spacer(2),\n              row([\n                box(label('Clarity'), 50, 20),\n                box(linear('kickFilterDropTime'), 90, 20, gray300, 4)\n              ]),\n              spacer(2),\n              row([\n                box(label('Mute'), 50, 20),\n                box(linear('kickAmplitudeDecayTime'), 90, 20, gray300, 4)\n              ]),\n            ]),\n            spacer(4),\n          ])\n        ]),\n        310,\n        'auto',\n        gray200,\n        8\n      ),\n    ]),\n    col([\n      box(\n        col([\n          spacer(5),\n          label('Toms'),\n          row([\n            col([\n              row([\n                knob('hiTomResonantPitch', { size: 30 }),\n                spacer(4),\n                knob('midTomResonantPitch', { size: 30 }),\n                spacer(4),\n                knob('lowTomResonantPitch', { size: 30 }),\n                spacer(4),\n                knob('flrTomResonantPitch', { size: 30 }),\n              ]),\n              spacer(4),\n              box(label('Resonant Pitches'), 120, 20, gray100, 4),\n            ]),\n            titled.knob('tomModPitch', 'Mod. Pitch', 30),\n            col([\n              row([\n                box(label('Pitch'), 50, 20),\n                box(linear('tomPitchDropTime'), 90, 20, gray300, 4),\n              ]),\n              spacer(2),\n              row([\n                box(label('Timbre'), 50, 20),\n                box(linear('tomWavetableScanTime'), 90, 20, gray300, 4)\n              ]),\n            ]),\n            col([\n              row([\n                box(label('Clarity'), 50, 20),\n                box(linear('tomFilterDropTime'), 90, 20, gray300, 4)\n              ]),\n              spacer(2),\n              row([\n                box(label('Mute'), 50, 20),\n                box(linear('tomAmplitudeDecayTime'), 90, 20, gray300, 4)\n              ]),\n            ]),\n            spacer(4),\n          ])\n        ]),\n        310,\n        'auto',\n        gray200,\n        8\n      ),\n    ]),\n  ]),\n  presetTags: {\n    'Genre': [\n      'Funk',\n      'Pop',\n      'Metal',\n      'Drum & Bass'\n    ]\n  }\n});\n\n	2	2023-03-24 15:09:51.368124	DrumAndDrummer.wavtool
38	Avg to Mono	1	const AvgToMonoEffect = ({ input, output }) => {\n  for (let i = 0; i < output.length; i ++) {\n    output[i] = (input[0][i] + input[1][i]) / 2;\n  }\n};\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: AvgToMonoEffect,\n  inputs: {\n    input: stereo('In'),\n  },\n  outputs: {\n    output: mono('Out'),\n  }\n});\n	3	2023-03-24 15:29:28.055775	AvgToMono.wavtool
39	MIDI-CV	1	const MIDIToCV = ({ input, gate, frequency, trigger }) => {\n  const voices = useRoundRobinVoicing(input, 1);\n  gate.set(voices[0].gate);\n  trigger.set(voices[0].trigger);\n  frequency.set(voices[0].frequency);\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: MIDIToCV,\n  inputs: {\n    input: midi('In')\n  },\n  outputs: {\n    frequency: expose(frequencySetting('Frequency', 1000)),\n    trigger: expose(zeroOneSetting('Trigger', 0)),\n    gate: expose(zeroOneSetting('Gate', 0)),\n  }\n});\n\n	4	2023-03-24 15:30:05.966422	MIDIToCV.wavtool
40	Math	1	const Effect = ({ input, output, min, max, curve }) => {\n  for (let i = 0; i < input.length; i++) {\n    output[i] = min[i] + (Math.pow(input[i], curve[i]) * (max[i] - min[i]));\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Effect,\n  inputs: {\n    input: expose(zeroOneSetting('In')),\n    curve: numberSetting('Curve', { defaultValue: 1, min: .5, max: 2, curve: Curve.Log2 }),\n    min: zeroOneSetting('Min', 0),\n    max: zeroOneSetting('Max', 1),\n  },\n  outputs: {\n    output: expose(zeroOneSetting('Out')),\n  },\n  layout: row([\n    titled.knob('min'),\n    titled.knob('max'),\n    titled.knob('curve'),\n  ])\n});\n	5	2023-03-24 15:31:00.701938	Math.wavtool
41	LFO	1	const LFO = ({ frequency, trigger, saw, sin, square, triangle, phase, noise }) => {\n  const sawOsc = useOscillator(Waveform.Sawtooth);\n  const sinOsc = useOscillator(Waveform.Sine);\n  const squareOsc = useOscillator(Waveform.Square);\n  const triangleOsc = useOscillator(Waveform.Triangle);\n  const noiseOsc = useOscillator(Waveform.Noise);\n  saw.set(sawOsc(frequency, phase, trigger));\n  sin.set(sinOsc(frequency, phase, trigger));\n  square.set(squareOsc(frequency, phase, trigger));\n  triangle.set(triangleOsc(frequency, phase, trigger));\n  noise.set(noiseOsc(frequency));\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: LFO,\n  inputs: {\n    frequency: expose(numberSetting('Frequency', { defaultValue: 1, min: 0.001, max: 100, curve: Curve.Log2, unit: InterfaceUnit.Hz })),\n    trigger: expose(zeroOneSetting('Trigger', 0)),\n    phase: expose(numberSetting('Phase', { defaultValue: 0, min: -Math.PI, max: Math.PI }))\n  },\n  outputs: {\n    saw: mono('Saw'),\n    sin: mono('Sine'),\n    square: mono('Square'),\n    triangle: mono('Triangle'),\n    noise: mono('Noise')\n  }\n});\n	7	2023-03-24 15:32:22.192783	LFO.wavtool
42	Mono to Stereo	1	const MonoToStereoEffect = ({ input, output }) => {\n  for (let i = 0; i < input.length; i ++) {\n    output[0][i] = input[i];\n    output[1][i] = input[i];\n  }\n};\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: MonoToStereoEffect,\n  inputs: {\n    input: mono('In'),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	8	2023-03-24 15:33:53.833444	MonoToStereo.wavtool
43	Envelope Generator	1	const Envelope = ({ trigger, gate, output, attack, decay, sustain, release }) => {\n  const processEnvelope = useEnvelope();\n  output.set(processEnvelope(\n    trigger,\n    gate,\n    attack,\n    decay,\n    sustain,\n    release,\n  ));\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Envelope,\n  inputs: {\n    trigger: expose(zeroOneSetting('Trigger')),\n    gate: expose(zeroOneSetting('Gate')),\n    attack: adsrTimeSetting('Attack', 1),\n    decay: adsrTimeSetting('Decay', 100),\n    sustain: zeroOneSetting('Sustain', 0.5),\n    release: adsrTimeSetting('Release', 300),\n  },\n  outputs: {\n    output: { ...mono('Out'), min: 0 },\n  }\n});\n	9	2023-03-24 15:34:39.194061	Envelope.wavtool
44	Envelope Follower	1	const gate = new Float64Array(bufferSize);\n\nconst EnvelopeFollower = ({ input, output, attack, decay, sustain, release, threshold, hold }) => {\n  const processEnvelope = useEnvelope();\n  const lastGate = useVal(0);\n  const trigger = useMonoSignal(input.length);\n  trigger.fill(0);\n  for (let i = 0; i < input.length; i++) {\n    const absInputSample = Math.abs(input[i]);\n    if (absInputSample > threshold[i]) {\n      gate[i] = 1;\n      if (lastGate.current !== 1) {\n        trigger[i] = 1;\n      }\n    } else if (absInputSample < threshold[i] * (1 - hold[i])) {\n      gate[i] = 0;\n    } else {\n      gate[i] = lastGate.current;\n    }\n    lastGate.current = gate[i];\n  }\n  output.set(\n    processEnvelope(\n      trigger,\n      gate,\n      attack,\n      decay,\n      sustain,\n      release,\n    )\n  );\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: EnvelopeFollower,\n  inputs: {\n    input: stereo('In'),\n    threshold: zeroOneSetting('Trigger', 0.3),\n    hold: zeroOneSetting('Hold', 0.3),\n    attack: adsrTimeSetting('Attack', 1),\n    decay: adsrTimeSetting('Decay', 100),\n    sustain: zeroOneSetting('Sustain', 0.5),\n    release: adsrTimeSetting('Release', 50),\n  },\n  outputs: {\n    output: { ...mono('Out'), min: 0 },\n  }\n});\n	10	2023-03-24 15:38:05.452958	EnvelopeFollower.wavtool
45	Tempo Delay	1	const TempoDelayEffect = ({ input, output, mix, feedback, beats }, { bpm }) => {\n  const delay = useDelay();\n  const timeSamples = useMonoSignal();\n  for (let i = 0; i < beats.length; i++) {\n    timeSamples[i] = beats[i] * (60 / bpm[i]) * sampleRate;\n  }\n  const delayed = delay(input, timeSamples, feedback);\n  for (let i = 0; i < input.length; i ++) {\n    output[i] = ((1 - mix[i]) * input[i]) + (mix[i] * delayed[i]);\n  }\n};\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: TempoDelayEffect,\n  inputs: {\n    input: stereo('In'),\n    mix: numberSetting('Mix', { defaultValue: 0.3, min: 0, max: 1 }),\n    feedback: numberSetting('Feedback', { defaultValue: 0.25, min: 0, max: 1 }),\n    beats: numberSetting('Beats', { defaultValue: 0.75, min: 0, max: 4 }),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	11	2023-03-24 15:38:24.121712	TempoDelay.wavtool
68	Channel Strip	1	const ChannelStripDevice = ({ input, output, gain, balance }) => {\n  for (let c = 0; c < input.length; c++) {\n    const inputChannel = input[c];\n    const outputChannel = output[c];\n    for (let s = 0; s < outputChannel.length; s++) {\n      const balanceMultiplier = c === 0\n        ? Math.min(1, -1 * (balance[s] - 1))\n        : Math.min(1, balance[s] + 1);\n      outputChannel[s] = inputChannel[s] * gain[s] * balanceMultiplier;\n    }\n  }\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: ChannelStripDevice,\n  inputs: {\n    input: stereo('In'),\n    gain: dbSetting('Gain', { defaultValue: 0, min: -Infinity, max: 12 }),\n    balance: numberSetting('Balance', { defaultValue: 0, min: -1, max: 1 }),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	34	2023-03-24 15:46:19.868219	ChannelStrip.wavtool
46	Time Delay	1	const sampleRateMultiplier = sampleRate / 1000;\n\nconst TimeDelayEffect = ({ input, output, mix, feedback, milliseconds }) => {\n  const delay = useDelay();\n  const timeSamples = useMonoSignal();\n  for (let i = 0; i < milliseconds.length; i++) {\n    timeSamples[i] = milliseconds[i] * (sampleRateMultiplier);\n  }\n  const delayed = delay(input, timeSamples, feedback);\n  for (let i = 0; i < input.length; i ++) {\n    output[i] = ((1 - mix[i]) * input[i]) + (mix[i] * delayed[i]);\n  }\n};\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: TimeDelayEffect,\n  inputs: {\n    input: stereo('In'),\n    mix: numberSetting('Mix', { defaultValue: 0.3, min: 0, max: 1 }),\n    feedback: numberSetting('Feedback', { defaultValue: 0.25, min: 0, max: 1 }),\n    milliseconds: numberSetting('Milliseconds', { defaultValue: 150, min: 0, max: 5000 }),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	12	2023-03-24 15:38:42.220044	TimeDelay.wavtool
47	Amplifier	1	const Effect = ({ input, output, gain }) => {\n  for (let i = 0; i < input.length; i++) {\n    output[i] = input[i] * dbToMultiplier(gain[i]);\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Effect,\n  inputs: {\n    input: stereo('In'),\n    gain: numberSetting('Gain', { defaultValue: 0, min: -35, max: 35, unit: InterfaceUnit.LiteralDecibels }),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	13	2023-03-24 15:39:00.071572	Amplifier.wavtool
48	Master Fader	1	const Effect = ({ input, output, gain }) => {\n  for (let i = 0; i < input.length; i++) {\n    output[i] = input[i] * gain[i];\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Effect,\n  inputs: {\n    input: stereo('In'),\n    gain: dbSetting('Master Volume', { defaultValue: 0, min: -Infinity, max: 6 }),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	14	2023-03-24 15:39:12.467252	MasterFader.wavtool
49	Stereo Inverter	1	const Effect = ({ input, output }) => {\n  for (let i = 0; i < input.length; i++) {\n    output[i] = input[i] * -1;\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Effect,\n  inputs: {\n    input: stereo('In'),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	15	2023-03-24 15:40:05.268061	StereoInverter.wavtool
50	EQ Band	1	const FFT_SIZE = 8192;\nconst BLOCK_SIZE = 2048;\n\nconst FADE_RATE = BLOCK_SIZE / 50000;\nconst RECOVER_RATE = BLOCK_SIZE / 200;\n\nconst EQ_BAND_MODES = {\n  'EQ Band': BiQuadFilterMode.EQBand,\n  'Low Pass': BiQuadFilterMode.LowPass,\n  'High Pass': BiQuadFilterMode.HighPass,\n  'Band Pass': BiQuadFilterMode.BandPass,\n  'Notch': BiQuadFilterMode.Notch,\n  'Low Shelf': BiQuadFilterMode.LowShelf,\n  'High Shelf': BiQuadFilterMode.HighShelf,\n}\n\nconst EQBand = ({ input, output, analysis, frequency, q, adaptiveQ, effectiveQ, gain, mode, interfaceVisible }) => {\n  const filterLeft = useBiQuadFilter();\n  const filterRight = useBiQuadFilter();\n\n  const adjustedQ = useMonoSignal(q.length);\n\n  if (adaptiveQ && mode === 'EQ Band') {\n    for (let i = 0; i < q.length; i ++) {\n      adjustedQ[i] = q[i] + (Math.abs(Math.log(gain[i])));\n    }\n  } else {\n    adjustedQ.set(q);\n  }\n\n  effectiveQ[0] = adjustedQ[0];\n\n  output[0].set(filterLeft(input[0], EQ_BAND_MODES[mode], frequency, adjustedQ, gain));\n  output[1].set(filterRight(input[1], EQ_BAND_MODES[mode], frequency, adjustedQ, gain));\n\n  const lastAnalysisResult = useMonoSignal(analysis.length);\n  const nextAnalysisResult = useMonoSignal(analysis.length);\n  const fft = useFFT(FFT_SIZE, BLOCK_SIZE);\n  const stepIndex = useVal(0);\n\n  if (!interfaceVisible) return;\n\n  for (let i = 0; i < output[0].length; i ++) {\n    fft.receiveSample((output[0][i] + output[1][i]) / sampleRate);\n    stepIndex.current ++;\n    if (fft.isProcessingStep()) {\n      fft.updateSpectrum();\n      stepIndex.current = 0;\n      lastAnalysisResult.set(nextAnalysisResult);\n      nextAnalysisResult.set(fft.getSpectrumOutput());\n    }\n  }\n\n  for (let a = 0; a < analysis.length; a ++) {\n    const progress = (stepIndex.current / BLOCK_SIZE);\n    const value = (\n      (lastAnalysisResult[a] * (1 - progress)) +\n      (nextAnalysisResult[a] * progress)\n    );\n    const faded = analysis[a] * (1-FADE_RATE);\n    analysis[a] = Math.max(\n      faded,\n      faded + (RECOVER_RATE * value)\n    )\n  }\n}\n\nconst Spectrum = (ctx, { analysis, frequency, effectiveQ, gain, mode }) => {\n  const w = ctx.canvas.width;\n  const h = ctx.canvas.height;\n  const focusH = h - 14;\n  // const imageData = ctx.getImageData(0, 0, w, h);\n  ctx.clearRect(0, 0, w, h);\n  // ctx.putImageData(imageData, 1, -1);\n  ctx.fillStyle = '#000000'\n  ctx.fillRect(0, 0, w, h);\n\n  ctx.strokeStyle = '#444';\n  ctx.lineWidth = 1;\n  renderSpectrumGrid(ctx, w, focusH);\n\n  const maxDB = 24;\n  const minDB = -24;\n\n  for (let i = maxDB; i >= minDB; i -= 12) {\n    ctx.beginPath();\n    const y = ((i - maxDB) / (minDB - maxDB)) * focusH;\n    ctx.moveTo(0, y);\n    ctx.lineTo(w, y);\n    ctx.stroke();\n    ctx.closePath();\n  }\n\n  ctx.fillStyle = '#666';\n  ctx.font = `10px Arial`;\n  labelSpectrumGrid(ctx, w, h);\n\n  ctx.lineWidth = 1;\n  ctx.strokeStyle = '#ffffff44';\n  ctx.fillStyle = '#ffffff11';\n  ctx.beginPath();\n  ctx.moveTo(-w,h);\n\n  traceSpectrum(ctx, analysis, FFT_SIZE, sampleRate, w, focusH, 'line');\n\n  ctx.lineTo(w * 2,h);\n  ctx.stroke();\n  ctx.fill();\n  ctx.closePath();\n\n  ctx.lineWidth = 2;\n  ctx.strokeStyle = blue500;\n  ctx.fillStyle = `${blue500}44`;\n  ctx.beginPath();\n\n  const transferFunction = getBiQuadFilterTransferFunction(\n    sampleRate,\n    EQ_BAND_MODES[mode],\n    frequency,\n    effectiveQ[0],\n    multiplierToDb(gain)\n  );\n\n  const zeroY = ((0 - maxDB) / (minDB - maxDB)) * focusH;\n  ctx.moveTo(-w, zeroY);\n  let x, y;\n\n  for (let i = 0; i < transferFunction.length; i ++) {\n    x = i / (transferFunction.length - 1) * w;\n    y = (transferFunction[i][1] - maxDB) / (minDB - maxDB) * focusH;\n\n    ctx.lineTo(x, y);\n  }\n\n  ctx.lineTo(w * 2, zeroY);\n\n  ctx.stroke();\n  ctx.fill();\n  ctx.closePath();\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: EQBand,\n  inputs: {\n    input: stereo('In'),\n    q: numberSetting('Q', { defaultValue: Math.sqrt(.5), min: .1, max: 18, curve: Curve.Log2 }),\n    frequency: frequencySetting('Frequency', 1000),\n    gain: biQuadFilterDbSetting('Gain', { defaultValue: 0, min: -24, max: 24 })\n  },\n  constants: {\n    adaptiveQ: booleanConstant('Adaptive Q', true),\n    mode: enumConstant('Mode', Object.keys(EQ_BAND_MODES), 'EQ Band')\n  },\n  outputs: {\n    output: stereo('Out')\n  },\n  sharedArrays: {\n    analysis: FFT_SIZE / 2,\n    effectiveQ: 1,\n  },\n  layout: themed(DeviceLayoutTheme.Skeuomorphic, col([\n    pinnable(canvas(\n      'spectrum',\n      Spectrum,\n      344,\n      150\n    )),\n    row([\n      titled.checkbox('adaptiveQ', 'Adapt.'),\n      titled.knob('q'),\n      titled.knob('frequency', 'Freq'),\n      titled.knob('gain'),\n      titled.selector('mode')\n    ])\n  ]))\n});\n	16	2023-03-24 15:40:34.852677	EQBand.wavtool
51	Compressor	1	const Compressor = ({ input, output, detector, threshold, ratio: inverseRatio, attack, release, gain: gainDB, gainReduction, detectorLevel, makeupGain }) => {\n  const appliedGainRef = useVal(0);\n\n  for (let i = 0; i < input[0].length; i ++) {\n    const thresholdDB = threshold[i];\n    const ratio = 1/inverseRatio[i];\n\n    const makeupGainAmount = makeupGain ? (-thresholdDB) * ((1 - ratio) / 2) : 0;\n\n    let rawAmplitude = 0;\n\n    for (let c = 0; c < detector.length; c ++) {\n      const channelAmplitude = Math.abs(detector[c][i]);\n      if (channelAmplitude > rawAmplitude) {\n        rawAmplitude = channelAmplitude;\n      }\n    }\n\n    const amplitudeDB = multiplierToDb(rawAmplitude)\n\n    const attackRate = 1/attack[i];\n    const releaseRate = 1/release[i];\n\n    const targetGainDB = amplitudeDB > thresholdDB\n      ? (thresholdDB - amplitudeDB) * (1 - ratio)\n      : 0;\n\n    const gainDelta = targetGainDB < appliedGainRef.current ? attackRate : releaseRate;\n\n    appliedGainRef.current = ((1 - gainDelta) * appliedGainRef.current) + (gainDelta * targetGainDB);\n    gainReduction[i] = appliedGainRef.current;\n\n    const gain = dbToMultiplier(appliedGainRef.current + gainDB[i] + makeupGainAmount);\n\n    detectorLevel[0] = Math.max(\n      rawAmplitude,\n      detectorLevel[0]\n    ) * (1 - (7/sampleRate));\n\n    for (let c = 0; c < input.length; c ++) {\n      output[c][i] = input[c][i] * gain;\n    }\n  }\n}\n\nconst CornerUI = (ctx, settings) => {\n  const width = ctx.canvas.width;\n  const height = ctx.canvas.height;\n\n  ctx.fillStyle = gray100;\n  roundedRect(ctx, 1, 1, ctx.canvas.width - 2, ctx.canvas.height - 2, 4, false, true);\n  ctx.clip();\n  ctx.fill();\n\n  const threshold = settings.threshold;\n  const thresholdProportion = 1 - ((threshold - 12) / (-64 - 12));\n\n  const ratio = (settings.ratio);\n\n  const thresholdY = height * (1 - thresholdProportion);\n  const thresholdX = width * (thresholdProportion);\n\n  const ratioEndY = (thresholdY * (1 - (1/ratio)));\n\n  ctx.strokeStyle = gray300;\n\n  ctx.beginPath();\n  for (let i = 12; i >= -64; i -= 12) {\n    const y = ((i - 12) / (-64 - 12)) * height;\n    ctx.moveTo(0, y);\n    ctx.lineTo(width, y);\n\n    const x = (1 - ((i - 12) / (-64 - 12))) * width;\n    ctx.moveTo(x, 0);\n    ctx.lineTo(x, height);\n  }\n  ctx.stroke();\n\n  ctx.strokeStyle = gray600;\n\n  ctx.lineWidth = 2;\n  ctx.fillStyle = '#fff';\n\n  ctx.beginPath();\n  ctx.moveTo(0, height);\n  ctx.lineTo(thresholdX, thresholdY);\n  ctx.lineTo(width, ratioEndY);\n  ctx.stroke();\n\n  const detectorLevel = 1 - ((multiplierToDb(settings.detectorLevel) - 12) / (-64 - 12));\n  const outputLevel = detectorLevel > thresholdProportion\n    ? thresholdProportion + ((detectorLevel - thresholdProportion) / ratio)\n    : detectorLevel;\n\n  ctx.beginPath();\n  ctx.arc(\n    detectorLevel * width,\n    (1 - outputLevel) * height,\n    6,\n    0,\n    2 * Math.PI\n  );\n  ctx.fill();\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: Compressor,\n  inputs: {\n    input: stereo('In'),\n    detector: stereo('Detector'),\n    gain: numberSetting('Gain', { defaultValue: 0, min: -12, max: 12 }),\n    threshold: numberSetting('Threshold', { defaultValue: -20, min: -64, max: 0, unit: InterfaceUnit.LiteralDecibels }),\n    ratio: numberSetting('Ratio', { defaultValue: 8, min: 1, max: 64, curve: Curve.Log2 }),\n    attack: adsrTimeSetting('Attack', 1),\n    release: adsrTimeSetting('Release', 30),\n  },\n  constants: {\n    makeupGain: booleanConstant('Makeup Gain', false)\n  },\n  outputs: {\n    output: stereo('Out'),\n  },\n  hidden: {\n    gainReduction: dbSetting('Gain Reduction', {})\n  },\n  sharedArrays: {\n    detectorLevel: 1\n  },\n  layout: themed(\n    DeviceLayoutTheme.Skeuomorphic,\n    row([\n      meter('input', 4),\n      col([\n        titled.knob('threshold'),\n        titled.knob('ratio'),\n        titled.knob('gain')\n      ]),\n      spacer(.5),\n      canvas(\n        'corner',\n        CornerUI,\n        200,\n        200\n      ),\n      spacer(.5),\n      col([\n        titled.knob('attack'),\n        titled.knob('release'),\n        titled.checkbox('makeupGain', 'Makeup')\n      ]),\n      meter('output', 4, true),\n    ])\n  )\n});\n	17	2023-03-24 15:41:07.702676	Compressor.wavtool
52	Crossfade	1	const Crossfade = ({ input1, input2, output, crossfade }) => {\n  for (let i = 0; i < input1.length; i ++) {\n    output[i] = (input1[i] * (1 - crossfade[i])) + (input2[i] * crossfade[i])\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Crossfade,\n  inputs: {\n    input1: stereo('In 1'),\n    input2: stereo('In 2'),\n    crossfade: numberSetting('Crossfade', { defaultValue: 0.5, min: 0, max: 1, curve: Curve.Linear }),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	18	2023-03-24 15:41:18.811717	Crossfade.wavtool
53	Reverb71	1	// Schroeder Reverb implementing the first diagram here: https://ccrma.stanford.edu/~jos/pasp/Example_Schroeder_Reverberators.html\nconst sampleRateMultiplier = (sampleRate / 25000);\n\nconst Reverb71 = ({ input, inputLevel, reverbLevel, output, time, mix, width }) => {\n  const reverb = useStereoSignal();\n\n  const allPass1 = useBiQuadFilter();\n  const allPass2 = useBiQuadFilter();\n  const allPass3 = useBiQuadFilter();\n\n  const delay1 = useDelay();\n  const delay2 = useDelay();\n  const delay3 = useDelay();\n  const delay4 = useDelay();\n\n  const monoInput = useMonoSignal();\n  const delay1Time = useMonoSignal();\n  const delay2Time = useMonoSignal();\n  const delay3Time = useMonoSignal();\n  const delay4Time = useMonoSignal();\n\n  for (let i = 0; i < time.length; i ++) {\n    delay1Time[i] = time[i] * sampleRateMultiplier * 901;\n    delay2Time[i] = time[i] * sampleRateMultiplier * 778;\n    delay3Time[i] = time[i] * sampleRateMultiplier * 1011;\n    delay4Time[i] = time[i] * sampleRateMultiplier * 1123;\n  }\n\n  for (let i = 0; i < input[0].length; i++) {\n    monoInput[i] = 0;\n    for (let c = 0; c < input.length; c ++) {\n      monoInput[i] += input[c][i];\n    }\n  }\n\n  const allpassed = allPass3(\n    allPass2(\n      allPass1(\n        monoInput,\n        BiQuadFilterMode.AllPass,\n        useConstantBuffer(125),\n        useConstantBuffer(.7),\n        useConstantBuffer(0)\n      ),\n      BiQuadFilterMode.AllPass,\n      useConstantBuffer(42),\n      useConstantBuffer(.7),\n      useConstantBuffer(0)\n    ),\n    BiQuadFilterMode.AllPass,\n    useConstantBuffer(12),\n    useConstantBuffer(.7),\n    useConstantBuffer(0)\n  );\n\n  const delayed1 = delay1(allpassed, delay1Time, useConstantBuffer(0.805));\n  const delayed2 = delay2(allpassed, delay2Time, useConstantBuffer(0.827));\n  const delayed3 = delay3(allpassed, delay3Time, useConstantBuffer(0.783));\n  const delayed4 = delay4(allpassed, delay4Time, useConstantBuffer(0.764));\n\n  for (let i = 0; i < input[0].length; i++) {\n    for (let c = 0; c < input.length; c ++) {\n      const widthScale = (2 * ((c / (input.length - 1)) - .5)) * width[i];\n      const delayed = (\n        (1 / 8) * (\n          (((widthScale * -1.00) + 1) / 2) * delayed1[i] +\n          (((widthScale * -0.33) + 1) / 2) * delayed2[i] +\n          (((widthScale *  0.33) + 1) / 2) * delayed3[i] +\n          (((widthScale *  1.00) + 1) / 2) * delayed4[i]\n        )\n      );\n      reverb[c][i] = delayed;\n      output[c][i] = delayed * mix[i] + input[c][i] * (1 - mix[i]);\n    }\n  }\n\n  writeSignalLevel(input, inputLevel);\n  writeSignalLevel(reverb, reverbLevel);\n};\n\nconst RenderReverbVis = (ctx, settings) => {\n  const w = ctx.canvas.width;\n  const h = ctx.canvas.height;\n  ctx.fillStyle = `${orange500}22`;\n  if (!ctx.hasRendered) {\n    ctx.hasRendered = true;\n    ctx.fillRect(0, 0, w, h);\n  }\n  const imgData = ctx.getImageData(1, 0, w-1, h);\n  ctx.putImageData(imgData, 0, 0);\n  ctx.clearRect(w-1, 0, 1, h);\n  ctx.fillRect(w-1, 0, 1, h);\n  const signalHeight = h/2;\n\n  const leftInputHeight = (settings.inputLevel[0]) * (signalHeight);\n  const rightInputHeight = (settings.inputLevel[0]) * (signalHeight);\n\n  ctx.fillStyle = `rgba(255,255,255,${settings.mix})`;\n  const leftOutputHeight = (settings.reverbLevel[0]) * (signalHeight);\n  const rightOutputHeight = (settings.reverbLevel[0]) * (signalHeight);\n  ctx.fillRect(\n    w - 1,\n    (h/2) - leftOutputHeight,\n    1,\n    leftOutputHeight + rightOutputHeight\n  );\n\n  ctx.strokeStyle = orange500;\n  ctx.beginPath();\n  ctx.moveTo(\n    w - 2,\n    ctx.lastLeft === null ? (h/2) : ctx.lastLeft\n  );\n  ctx.lineTo(\n    w - 1,\n    (h/2) - leftInputHeight,\n  );\n  ctx.closePath();\n  ctx.stroke();\n  ctx.lastLeft = (h/2) - leftInputHeight;\n\n  ctx.beginPath();\n  ctx.moveTo(\n    w - 2,\n    ctx.lastRight === null ? (h/2) : ctx.lastRight\n  );\n  ctx.lineTo(\n    w - 1,\n    (h/2) + rightInputHeight,\n  );\n  ctx.closePath();\n  ctx.stroke();\n  ctx.lastRight = (h/2) + rightInputHeight;\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: Reverb71,\n  inputs: {\n    input: stereo('In'),\n    time: numberSetting('Time', { defaultValue: 1, min: .1, max: 10, curve: Curve.Log2 }),\n    mix: numberSetting('Mix', { defaultValue: 1, min: 0, max: 1 }),\n    width: numberSetting('Width', { defaultValue: 1, min: 0, max: 1 })\n  },\n  outputs: {\n    output: stereo('Out'),\n  },\n  sharedArrays: {\n    inputLevel: 1,\n    reverbLevel: 1,\n  },\n  layout: themed(\n    DeviceLayoutTheme.Dark,\n    col([\n      canvas(\n        'visualizer',\n        RenderReverbVis,\n        250,\n        100\n      ),\n      spacer(0.25),\n      row([\n        titled.knob('time'),\n        titled.knob('mix'),\n        titled.knob('width')\n      ])\n    ])\n  )\n});\n	19	2023-03-24 15:41:33.051737	Reverb71.wavtool
54	Gate	1	const LOOKAHEAD_MS = 4;\nconst LOOKAHEAD_SAMPLES = Math.floor(LOOKAHEAD_MS * (sampleRate / 1000));\n\nconst Gate = ({ input, output, detector, threshold, floor, attack, release, hold }) => {\n  const appliedGainRef = useVal(0);\n  const peakHoldRef = useVal(0);\n\n  const delayedSignal = useDelayedStereo(input, LOOKAHEAD_SAMPLES);\n\n  for (let i = 0; i < delayedSignal[0].length; i ++) {\n    const thresholdDB = threshold[i];\n    const floorDB = floor[i];\n    const attackRate = 1/(attack[i] + LOOKAHEAD_MS);\n    const releaseRate = 1/release[i];\n    const holdRate = 1/hold[i];\n\n    let rawAmplitude = 0;\n\n    for (let c = 0; c < detector.length; c ++) {\n      const channelAmplitude = Math.abs(detector[c][i]);\n      if (channelAmplitude > rawAmplitude) {\n        rawAmplitude = channelAmplitude;\n      }\n    }\n\n    peakHoldRef.current = Math.max(peakHoldRef.current * (1 - holdRate), rawAmplitude);\n\n    const amplitudeDB = multiplierToDb(peakHoldRef.current)\n\n    let targetGainDB = amplitudeDB < thresholdDB\n      ? floorDB\n      : 0;\n\n    const gainDelta = targetGainDB < appliedGainRef.current ? releaseRate : attackRate;\n\n    appliedGainRef.current = Math.min(0, ((1 - gainDelta) * appliedGainRef.current) + (gainDelta * targetGainDB));\n\n    const gain = dbToMultiplier(appliedGainRef.current);\n\n    for (let c = 0; c < delayedSignal.length; c ++) {\n      output[c][i] = delayedSignal[c][i] * gain;\n    }\n  }\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: Gate,\n  inputs: {\n    input: stereo('In'),\n    detector: stereo('Detector'),\n    threshold: numberSetting('Threshold', { defaultValue: -20, min: -64, max: 0, unit: InterfaceUnit.LiteralDecibels }),\n    floor: numberSetting('Floor', { defaultValue: -40, min: -80, max: 0, unit: InterfaceUnit.LiteralDecibels }),\n    attack: adsrTimeSetting('Attack', 0.1),\n    hold: adsrTimeSetting('Hold', 10),\n    release: adsrTimeSetting('Release', 15),\n  },\n  outputs: {\n    output: stereo('Out', LOOKAHEAD_SAMPLES),\n  }\n});\n	20	2023-03-24 15:41:49.577426	Gate.wavtool
55	Transient Splitter	1	const TransientSplitter = ({ input, attack, sustain, speed, sensitivity }) => {\n  const slowAmplitude = useVal(0);\n  const transientness = useVal(0);\n  const vuMeter = useVUFollower();\n\n  for (let i = 0; i < input[0].length; i ++) {\n    const fastAmplitude = Math.max(...input.map(channel => Math.abs(channel[i])));\n    slowAmplitude.current = vuMeter(fastAmplitude);\n\n    if ((fastAmplitude / slowAmplitude.current) > (1 - Math.pow(sensitivity[i] / 10, 2)) * 14) {\n      transientness.current = 1;\n    }\n\n    for (let c = 0; c < input.length; c ++) {\n      attack[c][i] = transientness.current * input[c][i];\n      sustain[c][i] = (1 - transientness.current) * input[c][i];\n    }\n\n    transientness.current *= 1 - Math.pow(10 - speed[i], -4);\n  }\n};\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: TransientSplitter,\n  inputs: {\n    input: stereo('In'),\n    speed: numberSetting('Speed', { defaultValue: 4, min: 0, max: 10 }),\n    sensitivity: numberSetting('Sensitivity', { defaultValue: 9, min: 0, max: 10 }),\n  },\n  outputs: {\n    attack: stereo('Attack'),\n    sustain: stereo('Sustain'),\n  }\n});\n	21	2023-03-24 15:42:05.94488	TransientSplitter.wavtool
56	Frequency Splitter	1	const FrequencySplitter = ({ input, low, high, q, frequency }) => {\n  const filter = useBiQuadFilter();\n  const filtered = filter(input, BiQuadFilterMode.LowPass, frequency, q, useConstantBuffer(0));\n  low.set(filtered);\n\n  for (let i = 0; i < input.length; i ++) {\n    high[i] = input[i] - low[i];\n  }\n};\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: FrequencySplitter,\n  inputs: {\n    input: stereo('In'),\n    q: numberSetting('Q', { defaultValue: 1, min: 1/16, max: 8, curve: Curve.Log2 }),\n    frequency: frequencySetting('Frequency', 1000),\n  },\n  outputs: {\n    high: stereo('High'),\n    low: stereo('Low'),\n  }\n});\n	22	2023-03-24 15:42:24.576711	FrequencySplitter.wavtool
57	Amplitude Splitter	1	const fftSize = 512;\nconst windowSize = 64;\nconst fftSizeTimesTwo = fftSize * 2;\n\nconst getArray = () => new Array(fftSize).fill(0);\n\nconst AmplitudeSplitter = ({ input, soft, loud, attack, release, threshold }) => {\n  const loudProcessor = useFFT(fftSize, windowSize);\n  const softProcessor = useFFT(fftSize, windowSize);\n  const attackAccumulation = useVal(0);\n  const releaseAccumulation = useVal(0);\n  const multipliers = useReturnedConst(getArray);\n  for (let i = 0; i < input.length; i ++) {\n    loudProcessor.receiveSample(input[i]);\n    softProcessor.receiveSample(input[i]);\n\n    const loudOut = loudProcessor.getComplexOutput();\n    const softOut = softProcessor.getComplexOutput();\n\n    attackAccumulation.current += 1/attack[i];\n    releaseAccumulation.current += 1/release[i];\n\n    if (loudProcessor.isProcessingStep()) {\n      const attackRate = attackAccumulation.current;\n      const releaseRate = releaseAccumulation.current;\n\n      attackAccumulation.current = 0;\n      releaseAccumulation.current = 0;\n\n      for (let j = 0; j < fftSizeTimesTwo; j += 2) {\n        const multiplierIndex = j / 2;\n        const bias = 1 - ((j / fftSizeTimesTwo) * (j / fftSizeTimesTwo));\n        const magnitude = Math.sqrt(loudOut[j] * loudOut[j] + loudOut[j+1] * loudOut[j+1]) * (1/(1 + bias));\n\n        if (magnitude < threshold[i] && multipliers[multiplierIndex] > 0) {\n          multipliers[multiplierIndex] = Math.max(0, (1 - releaseRate) * multipliers[multiplierIndex]);\n        } else if (magnitude > threshold[i] && multipliers[multiplierIndex] < 1) {\n          multipliers[multiplierIndex] = Math.min(1, ((1 - attackRate) * multipliers[multiplierIndex]) + (attackRate));\n        }\n      }\n\n      for (let j = 0; j < fftSizeTimesTwo; j += 2) {\n        const multiplierIndex = j / 2;\n        loudOut[j]   *= multipliers[multiplierIndex];\n        loudOut[j+1] *= multipliers[multiplierIndex];\n\n        softOut[j]   *= (1 - multipliers[multiplierIndex]);\n        softOut[j+1] *= (1 - multipliers[multiplierIndex]);\n      }\n    }\n\n    loud[i] = loudProcessor.produceSample();\n    soft[i] = softProcessor.produceSample();\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: AmplitudeSplitter,\n  inputs: {\n    input: stereo('In'),\n\n    threshold: numberSetting('Threshold', { defaultValue: 2, min: 0.01, max: 100, curve: Curve.Log2 }),\n    attack: adsrTimeSetting('Attack', 10),\n    release: adsrTimeSetting('Release', 100),\n  },\n  outputs: {\n    loud: stereo('Loud', computeFFTDelay(fftSize, windowSize)),\n    soft: stereo('Quiet', computeFFTDelay(fftSize, windowSize)),\n  }\n});\n	23	2023-03-24 15:42:39.749297	AmplitudeSplitter.wavtool
58	Overdrive125	1	const Overdrive125 = ({ input, output, pre, post }) => {\n  for (let i = 0; i < input.length; i ++) {\n    const x = input[i] * pre[i];\n    const absX = Math.abs(x);\n    const sign = x > 0 ? 1 : -1;\n    const postMultiplier = post[i];\n    if (absX < 1/3) {\n      output[i] = 2 * x * postMultiplier;\n    } else if (absX < 2/3) {\n      output[i] = ((3 - Math.pow((2 - (3 * absX)), 2)) / 3) * sign * postMultiplier;\n    } else {\n      output[i] = sign * postMultiplier;\n    }\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Overdrive125,\n  inputs: {\n    input: stereo('In'),\n    pre: dbSetting('Pre-Gain', { defaultValue: 16, min: -24, max: 24 }),\n    post: dbSetting('Post-Gain', { defaultValue: -3, min: -24, max: 24 })\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	24	2023-03-24 15:43:22.924229	Overdrive125.wavtool
59	Distortion127	1	const Distortion127 = ({ input, output, pre, post }) => {\n  for (let i = 0; i < input.length; i ++) {\n    const x = input[i] * pre[i];\n    const absX = Math.abs(x);\n    const sign = x > 0 ? 1 : -1;\n    output[i] = sign * (1 - Math.pow(Math.E, -1 * absX)) * post[i];\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Distortion127,\n  inputs: {\n    input: stereo('In'),\n    pre: dbSetting('Pre-Gain', { defaultValue: 16, min: -24, max: 24 }),\n    post: dbSetting('Post-Gain', { defaultValue: -8, min: -24, max: 24 })\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	25	2023-03-24 15:43:38.200503	Distortion127.wavtool
60	Tube122	1	const Tube122 = ({ input, output, pre, dist, q, post }) => {\n  const filter = useBiQuadFilter();\n  for (let i = 0; i < input.length; i ++) {\n    const x = input[i] * pre[i];\n    if (q[i] === 0) {\n      if (x === 0) {\n        output[i] = 1/dist[i];\n      } else {\n        output[i] = x / (1 - Math.exp(-dist[i] * x));\n      }\n    } else {\n      if (x === q[i]) {\n        output[i] = 1 / dist[i] + q / (1 - Math.exp(dist[i] * q[i]));\n      } else {\n        output[i] = (x - q[i]) / (1 - Math.exp((-dist[i] * (x - q[i])))) + q[i] / (1 - Math.exp(dist[i] * q[i]));\n      }\n    }\n  }\n  const filtered = filter(output, BiQuadFilterMode.HighPass, useConstantBuffer(20), useConstantBuffer(1), useConstantBuffer(0));\n  for (let i = 0; i < output.length; i ++) {\n    output[i] = filtered[i] * post[i];\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Tube122,\n  inputs: {\n    input: stereo('In'),\n    pre: dbSetting('Pre-Gain', { defaultValue: 16, min: -24, max: 24 }),\n    dist: numberSetting('Dist', { defaultValue: 1, min: 1, max: 10 }),\n    q: numberSetting('Q', { defaultValue: -.1, min: -2, max: 2 }),\n    post: dbSetting('Post-Gain', { defaultValue: -12, min: -24, max: 24 }),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	26	2023-03-24 15:43:49.562946	Tube122.wavtool
61	Audio Sum	1	const AudioSum = ({ inputs, output }) => {\n  const inputArray = Object.values(inputs);\n\n  output.fill(0);\n\n  for (let i = 0; i < inputArray.length; i++) {\n    const inputChannel = inputArray[i];\n    for (let s = 0; s < inputChannel.length; s++) {\n      output[s] += inputChannel[s];\n    }\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: AudioSum,\n  inputs: {\n    inputs: repeatable(stereo('In #'))\n  },\n  outputs: {\n    output: stereo('Out')\n  }\n});\n	27	2023-03-24 15:44:00.317468	AudioSum.wavtool
62	Sampler	1	const SampleVoice = ({ velocity, trigger, gate }, { attack, decay, sustain, release, output, buffer }) => {\n  const readIndex = useVal(0);\n  const isPlaying = useVal(false);\n\n  const channelBuffer = buffer.channelDataList[0];\n\n  const processEnvelope = useEnvelope();\n  const amplitude = processEnvelope(trigger, gate, attack, decay, sustain, release);\n  const totalAmplitude = useMonoSignal();\n  multiply(amplitude, velocity, totalAmplitude);\n  const voiceOutput = useMonoSignal();\n\n  if (channelBuffer.length <= 1) return;\n\n  voiceOutput.fill(0);\n  for (let i = 0; i < output.length; i ++) {\n    if (trigger[i] === 1) {\n      isPlaying.current = true;\n      readIndex.current = 0;\n    } else if (amplitude[i] === 0) {\n      isPlaying.current = false;\n    }\n\n    if (isPlaying.current) {\n      if (readIndex.current < channelBuffer.length) {\n        voiceOutput[i] += channelBuffer[readIndex.current];\n        readIndex.current ++;\n      }\n    }\n  }\n  multiply(voiceOutput, totalAmplitude, voiceOutput);\n  add(output, voiceOutput, output);\n}\n\nconst sampleIds = ['sample1', 'sample2', 'sample3', 'sample4', 'sample5', 'sample6', 'sample7', 'sample8'];\n\nconst getSampleData = (scope) => {\n  const results = [];\n  for (let i = 0; i < sampleIds.length; i ++) {\n    const pitches = [];\n    for (let j = 0; j < 88; j += 12) {\n      pitches.push(j + i)\n    }\n    results.push({\n      pitches,\n      scope: {\n        ...scope,\n        buffer: scope[sampleIds[i]]\n      }\n    });\n  }\n  return results;\n}\n\nconst SamplerInstrument = (scope, globals) => {\n  scope.output.fill(0);\n  const sampleData = useReturnedConst(getSampleData, scope);\n\n  for (let i = 0; i < sampleData.length; i ++) {\n    const data = sampleData[i];\n    const voice = useSingleVoice(scope.input, data.pitches);\n    SampleVoice(voice, data.scope);\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  noteInputHint: {\n    interface: NoteInterfaceType.Pads,\n    usedPitches: {\n      48: 'Sample 1',\n      50: 'Sample 2',\n      52: 'Sample 3',\n      53: 'Sample 4',\n\n      55: 'Sample 5',\n      57: 'Sample 6',\n      59: 'Sample 7',\n      60: 'Sample 8',\n    }\n  },\n  implementation: SamplerInstrument,\n  inputs: {\n    input: midi('In'),\n    attack: adsrTimeSetting('Attack', 1),\n    decay: adsrTimeSetting('Decay', 100),\n    sustain: zeroOneSetting('Sustain', 0.25),\n    release: adsrTimeSetting('Release', 50),\n  },\n  outputs: {\n    output: stereo('Out')\n  },\n  constants: {\n    sample1: audioBufferConstant('Sample 1'),\n    sample2: audioBufferConstant('Sample 2'),\n    sample3: audioBufferConstant('Sample 3'),\n    sample4: audioBufferConstant('Sample 4'),\n\n    sample5: audioBufferConstant('Sample 5'),\n    sample6: audioBufferConstant('Sample 6'),\n    sample7: audioBufferConstant('Sample 7'),\n    sample8: audioBufferConstant('Sample 8'),\n  },\n  layout: col([\n    row([\n      audioBufferPicker('sample5'),\n      audioBufferPicker('sample6'),\n      audioBufferPicker('sample7'),\n      audioBufferPicker('sample8'),\n    ]),\n    row([\n      audioBufferPicker('sample1'),\n      audioBufferPicker('sample2'),\n      audioBufferPicker('sample3'),\n      audioBufferPicker('sample4'),\n    ]),\n    themed(DeviceLayoutTheme.Skeuomorphic, row([\n      titled.knob('attack'),\n      titled.knob('decay'),\n      titled.knob('sustain'),\n      titled.knob('release'),\n    ]))\n  ])\n});\n\n	28	2023-03-24 15:44:20.026089	SamplerInstrument.wavtool
63	Simple Visualizer	1	const MeterInputs = ({ input, inputMeter }) => {\n  writeSignalLevel(input, inputMeter, 3);\n}\n\nconst Visualizer = (ctx, settings, { clock, playing }) => {\n  ctx.fillStyle = gray100;\n  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);\n  const inputLevel = (settings.inputMeter[0]);\n  if (playing) {\n    const beatProgress = clock % 1;\n    if (beatProgress < 0.5) {\n      ctx.fillStyle = `rgba(255,255,255,${settings.brightness + (0.5 - beatProgress)})`;\n    } else {\n      ctx.fillStyle = `rgba(255,255,255,${settings.brightness})`;\n    }\n  } else {\n    ctx.fillStyle = `rgba(255,255,255,${settings.brightness})`;\n  }\n\n  ctx.beginPath();\n  ctx.arc(ctx.canvas.width/2, ctx.canvas.height/2, inputLevel * ctx.canvas.width/4, 0, 2 * Math.PI);\n  ctx.fill();\n};\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: MeterInputs,\n  inputs: {\n    input: stereo('In'),\n    brightness: expose(zeroOneSetting('Brightness', .5))\n  },\n  sharedArrays: {\n    inputMeter: 1\n  },\n  layout: row([\n    titled.knob('brightness'),\n    pinnable(canvas(\n      'main',\n      Visualizer,\n      200,\n      200\n    ))\n  ])\n});\n	29	2023-03-24 15:44:45.010775	SimpleVisualizer.wavtool
64	Simple Synth	1	const NUMBER_OF_VOICES = 8;\nconst VOICE_SCALE = 1 + Math.sqrt(NUMBER_OF_VOICES);\nconst vibratoFrequency = new Float64Array(bufferSize).fill(5);\n\nconst SimpleSynth = ({ input, attack, decay, sustain, release, transpose, output }) => {\n  output.fill(0);\n  const voices = useRoundRobinVoicing(input, NUMBER_OF_VOICES);\n\n  const vibratoOscillator = useOscillator(Waveform.Sine);\n  const vibratoOutput = vibratoOscillator(vibratoFrequency);\n\n  for (let v = 0; v < voices.length; v ++) {\n    const oscillator = useOscillator(Waveform.Sawtooth, true);\n    const processEnvelope = useEnvelope();\n    const gate = voices[v].gate;\n    const trigger = voices[v].trigger;\n    const frequency = useMonoSignal(voices[v].frequency.length);\n    for (let i = 0; i < frequency.length; i ++) {\n      frequency[i] = (\n        voices[v].frequency[i] *\n        Math.pow(\n          2,\n          (\n            input.ccs.pitchBend[i] +\n            (vibratoOutput[i] * (input.ccs.modulationWheel[i] + input.ccs.channelPressure[i]))\n          ) / 12\n        )\n      );\n    }\n    const velocity = voices[v].velocity;\n    const oscillatorOutput = oscillator(frequency);\n    const amplitude = processEnvelope(trigger, gate, attack, decay, sustain, release);\n    for (let i = 0; i < oscillatorOutput.length; i ++) {\n      output[i] += oscillatorOutput[i] * amplitude[i] * velocity[i] / VOICE_SCALE;\n    }\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: SimpleSynth,\n  inputs: {\n    input: midi('In'),\n    attack: adsrTimeSetting('Attack', 1),\n    decay: adsrTimeSetting('Decay', 100),\n    sustain: zeroOneSetting('Sustain', 0.5),\n    release: adsrTimeSetting('Release', 50),\n    transpose: numberSetting('Transpose', { defaultValue: 0, min: -1, max: 1 })\n  },\n  outputs: {\n    output: stereo('Out')\n  }\n});\n\n	30	2023-03-24 15:45:03.261505	SimpleSynth.wavtool
65	Oscilloscope	1	const phaseInput = new Float64Array(bufferSize);\nconst ReadOscilloscopeInputs = ({ input, frequency, trigger, inputOscilloscope }) => {\n  const lastWriteIndex = useVal(0);\n  const oscillator = useOscillator(Waveform.Sawtooth);\n  const oscillatorOutput = oscillator(frequency, phaseInput, trigger);\n  const lastOscillatorOutput = useVal(-1);\n  for (let i = 0; i < input[0].length; i ++) {\n    if (lastWriteIndex.current < inputOscilloscope.length) {\n      inputOscilloscope[lastWriteIndex.current] = input[0][i];\n      lastWriteIndex.current ++\n    } else if (lastOscillatorOutput.current <= 0 && oscillatorOutput[i] > 0) {\n      lastWriteIndex.current = 0;\n    }\n    lastOscillatorOutput.current = oscillatorOutput[i];\n  }\n}\n\nconst Visualizer = (ctx, settings) => {\n  const h = ctx.canvas.height;\n  const w = ctx.canvas.width;\n  const imageData = ctx.getImageData(0, 0, w, h);\n  ctx.clearRect(0, 0, w, h);\n  ctx.putImageData(imageData, 0, -1);\n  ctx.fillStyle = `${gray100}42`;\n  let max = 0;\n  for (let i = 0; i < settings.inputOscilloscope.length; i ++) {\n    max = Math.max(max, Math.abs(settings.inputOscilloscope[i]));\n  }\n  ctx.strokeStyle = `rgba(\n    ${Math.floor((settings.frequency + 80) % 255)},\n    ${Math.floor((settings.frequency + 160) % 255)},\n    ${Math.floor(settings.frequency % 255)},\n    ${max}\n  )`;\n\n  //  ctx.fillRect(0, 0, w, h);\n  ctx.beginPath();\n  ctx.moveTo(-w, h);\n  ctx.lineTo(-w, 9*h/10);\n  for (let i = 0; i < settings.inputOscilloscope.length; i ++) {\n    ctx.lineTo(\n      (i / settings.inputOscilloscope.length) * w,\n      9*h/10 + (settings.inputOscilloscope[i] * 0.2 * h/2)\n    );\n  }\n  ctx.lineTo(w*2, h/2);\n  ctx.lineTo(w*2, h);\n  ctx.closePath();\n  ctx.stroke();\n  ctx.fill();\n};\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: ReadOscilloscopeInputs,\n  inputs: {\n    input: stereo('In'),\n    frequency: expose(frequencySetting('Frequency', 1000)),\n    trigger: expose(zeroOneSetting('Trigger', 0)),\n  },\n  sharedArrays: {\n    inputOscilloscope: 1024\n  },\n  layout: row([\n    pinnable(canvas(\n      'main',\n      Visualizer,\n      400,\n      200\n    ))\n  ])\n});\n	31	2023-03-24 15:45:14.380856	Oscilloscope.wavtool
66	Psychedelic Christmas	1	const beatsPerRotation = 8;\nconst numberOfRotationalBars = 120;\nconst numberOfBackgroundBars = 40;\n\nconst MeterInputs = ({ white, green, red, whiteLevel, greenLevel, redLevel }) => {\n  writeSignalLevel(white, whiteLevel);\n  writeSignalLevel(red, redLevel);\n  writeSignalLevel(green, greenLevel);\n}\n\nconst colors = {\n  'whiteLevel': 'white',\n  'redLevel': 'red',\n  'greenLevel': 'green',\n};\n\nconst Visualizer = (ctx, settings) => {\n  if (!ctx.flakes || !ctx.bars) {\n    ctx.flakes = [];\n    ctx.bars = {};\n  }\n  const now = Date.now();\n  const rotation = ((numberOfRotationalBars * (now / 1000) * (100/60) / beatsPerRotation)) % numberOfRotationalBars;\n  const roundedRotation = Math.floor(rotation);\n  const w = ctx.canvas.width;\n  const h = ctx.canvas.height;\n  const minD = Math.min(w, h);\n  ctx.fillStyle = gray100;\n  ctx.fillRect(0, 0, w, h);\n\n  if (!ctx.bars[roundedRotation]) {\n    ctx.bars[roundedRotation] = {};\n  }\n\n  ['redLevel', 'greenLevel', 'whiteLevel'].forEach((c, i) => {\n    if (!ctx.bars[roundedRotation][c]) {\n      ctx.bars[roundedRotation][c] = {};\n    }\n    ctx.bars[roundedRotation][c].m = Math.pow(settings[c], 2);\n    ctx.bars[roundedRotation][c].t = now;\n  });\n\n  for (let i = 0; i < numberOfBackgroundBars; i ++) {\n    const m = (1 + (Math.sin((i/numberOfBackgroundBars) * 2 * Math.PI + (now / 1000)))) / 4 + 1/4;\n    ctx.fillStyle = gray150;\n    ctx.fillRect(\n      (w * (1/numberOfBackgroundBars) * i) + 1,\n      h/2,\n      (w * 1/numberOfBackgroundBars) - 2,\n      (h/2) * m\n    );\n    ctx.fillRect(\n      (w * (1/numberOfBackgroundBars) * i) + 1,\n      h/2 * (1 - m),\n      (w * 1/numberOfBackgroundBars) - 2,\n      (h/2) * m\n    );\n  }\n\n  Object.keys(ctx.bars).forEach((r) => {\n    ctx.save();\n    ctx.translate(w/2, h/2);\n    ctx.rotate((Number(r) / numberOfRotationalBars) * Math.PI * 2);\n    ctx.translate(-w/2, -h/2);\n    Object.keys(ctx.bars[r]).forEach((c, i, l) => {\n      const stackDepth = (l.length - i);\n      const beamWidth = stackDepth * (minD / 500);\n      ctx.fillStyle = colors[c];\n      const barHeight = (\n        ((minD * ctx.bars[r][c].m) / 4)\n        *\n        (1 - Math.max(0, Math.min(1, (now - ctx.bars[r][c].t) / 4000)))\n      ) + beamWidth;\n      if (barHeight >= beamWidth) {\n        ctx.beginPath();\n        const xOffset = Math.sin(i + (now / 1000)) * (minD / 20);\n        const yOffset = Math.cos((i * .5) + (now / 1000)) * (minD / 20);\n        roundedRect(\n          ctx,\n          xOffset + (w/2) - beamWidth,\n          yOffset + (\n            (minD/2) +\n            (stackDepth * minD/8) -\n            barHeight / 2\n          ),\n          beamWidth * 2,\n          barHeight,\n          beamWidth\n        );\n        // don't wobble around:\n        // roundedRect(\n        //   ctx,\n        //   (w/2) - beamWidth,\n        //   (\n        //     (minD/2) +\n        //     (stackDepth * minD/8) -\n        //     barHeight / 2\n        //   ),\n        //   beamWidth * 2,\n        //   barHeight,\n        //   beamWidth\n        // );\n        ctx.fill();\n      }\n    });\n    ctx.restore();\n  });\n\n  if (Math.random() < settings.snow) {\n    ctx.flakes.push({\n      v: 0.4,\n      x: Math.random() * w,\n      c: 0,\n      y: 0\n    });\n  }\n\n  ctx.fillStyle = '#fff';\n\n  ctx.flakes.forEach((f, i) => {\n    ctx.fillRect(f.x, f.y, 2, 2);\n    f.y += f.v;\n    f.x += Math.sin(((i * 90) + (f.y * 5) + Date.now()) / 1000) / 8\n  });\n\n  for (let i = ctx.flakes.length - 1; i >= 0; i --) {\n    if (ctx.flakes[i].y >= h) {\n      ctx.flakes.splice(i, 1);\n    }\n  }\n};\n\nthis.constructDevice = () => constructPerBufferDevice({\n  // no audio processing.\n  implementation: MeterInputs,\n  inputs: {\n    white: stereo('White'),\n    green: stereo('Green'),\n    red: stereo('Red'),\n    snow: zeroOneSetting('Snowiness', 0.1)\n  },\n  sharedArrays: {\n    whiteLevel: 1,\n    greenLevel: 1,\n    redLevel: 1,\n  },\n  layout: row([\n    titled.knob('snow'),\n    pinnable(canvas(\n      'main',\n      Visualizer,\n      200,\n      200\n    ))\n  ])\n});\n	32	2023-03-24 15:45:29.36953	PsychedelicChristmas.wavtool
69	Master Bus	1	const MasterSum = ({ inputs, output }) => {\n  const inputArray = Object.values(inputs);\n\n  output.fill(0);\n\n  for (let i = 0; i < inputArray.length; i++) {\n    const inputChannel = inputArray[i];\n    for (let s = 0; s < inputChannel.length; s++) {\n      output[s] += inputChannel[s];\n    }\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: MasterSum,\n  outputs: {\n    output: stereo('Out')\n  },\n  hidden: {\n    inputs: { type: 'Stereo', name: 'In #', repeatable: true }\n  }\n});\n	35	2023-03-24 15:46:35.925688	MasterSum.wavtool
33	Metronome	1	const attack = new Float64Array(bufferSize).fill(0);\nconst sustain = new Float64Array(bufferSize);\nconst gate = new Float64Array(bufferSize);\nconst trigger = new Float64Array(bufferSize);\nconst frequency = new Float64Array(bufferSize);\n\nconst Metronome = ({\n  clickLength,\n  downbeatFrequency,\n  otherBeatFrequency,\n  frequencyDropAmount,\n  amplitude,\n  output\n}, {\n  clock,\n  playing,\n  beatNumerator,\n  metronome,\n  bpm\n}) => {\n  const oscillator = useOscillator(Waveform.Sine, true);\n  const envelope = useEnvelope();\n  output[0].fill(0);\n  output[1].fill(0);\n  trigger.fill(0);\n  gate.fill(0);\n  const lastBeat = useVal(-1);\n\n  if (playing && metronome) {\n    for (let i = 0; i < clock.length; i++) {\n      const wholeBeat = Math.floor(clock[i]);\n      const beatsPerSample = (60/bpm[i])/sampleRate;\n      trigger[i] = (wholeBeat !== lastBeat.current && clock[i] % 1 < (beatsPerSample * 10)) ? 1 : 0;\n      gate[i] = clock[i] % 1 < 0.5 ? 1 : 0\n      lastBeat.current = wholeBeat;\n    }\n  }\n\n  const envelopeOutput = envelope(trigger, gate, attack, clickLength, sustain, clickLength);\n\n  for (let i = 0; i < frequency.length; i ++) {\n    const beatNumberInBar = Math.floor(((clock[i] % beatNumerator) + beatNumerator) % beatNumerator);\n    frequency[i] = (\n      (beatNumberInBar === 0 ? downbeatFrequency[i] : otherBeatFrequency[i]) *\n      (((frequencyDropAmount[i]) * envelopeOutput[i]) + (1 - frequencyDropAmount[i]))\n    );\n  }\n\n  const oscillatorOutput = oscillator(frequency, useConstantBuffer(0), trigger);\n\n  for (let i = 0; i < output[0].length; i ++) {\n    output[0][i] = oscillatorOutput[i] * amplitude[i] * envelopeOutput[i];\n    output[1][i] = output[0][i]\n  }\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: Metronome,\n  inputs: {\n    clickLength: collapse(adsrTimeSetting('Click Length', 20)),\n    downbeatFrequency: collapse(frequencySetting('Downbeat', 1500)),\n    otherBeatFrequency: collapse(frequencySetting('Other Beat', 750)),\n    frequencyDropAmount: collapse(numberSetting('Drop', { defaultValue: 0.4, min: 0, max: 1, curve: Curve.Linear })),\n    amplitude: collapse(numberSetting('Amplitude', { defaultValue: 0.75, min: 0, max: 1, curve: Curve.Linear }))\n  },\n  outputs: {\n    output: stereo('Output')\n  }\n});\n	\N	2023-03-24 15:45:42.54947	2bfdb690-b27e-441e-a2eb-f820b907f78e
67	Metronome	1	const attack = new Float64Array(bufferSize).fill(0);\nconst sustain = new Float64Array(bufferSize);\nconst gate = new Float64Array(bufferSize);\nconst trigger = new Float64Array(bufferSize);\nconst frequency = new Float64Array(bufferSize);\n\nconst Metronome = ({\n  clickLength,\n  downbeatFrequency,\n  otherBeatFrequency,\n  frequencyDropAmount,\n  amplitude,\n  output\n}, {\n  clock,\n  playing,\n  beatNumerator,\n  metronome,\n  bpm\n}) => {\n  const oscillator = useOscillator(Waveform.Sine, true);\n  const envelope = useEnvelope();\n  output[0].fill(0);\n  output[1].fill(0);\n  trigger.fill(0);\n  gate.fill(0);\n  const lastBeat = useVal(-1);\n\n  if (playing && metronome) {\n    for (let i = 0; i < clock.length; i++) {\n      const wholeBeat = Math.floor(clock[i]);\n      const beatsPerSample = (60/bpm[i])/sampleRate;\n      trigger[i] = (wholeBeat !== lastBeat.current && clock[i] % 1 < (beatsPerSample * 10)) ? 1 : 0;\n      gate[i] = clock[i] % 1 < 0.5 ? 1 : 0\n      lastBeat.current = wholeBeat;\n    }\n  }\n\n  const envelopeOutput = envelope(trigger, gate, attack, clickLength, sustain, clickLength);\n\n  for (let i = 0; i < frequency.length; i ++) {\n    const beatNumberInBar = Math.floor(((clock[i] % beatNumerator) + beatNumerator) % beatNumerator);\n    frequency[i] = (\n      (beatNumberInBar === 0 ? downbeatFrequency[i] : otherBeatFrequency[i]) *\n      (((frequencyDropAmount[i]) * envelopeOutput[i]) + (1 - frequencyDropAmount[i]))\n    );\n  }\n\n  const oscillatorOutput = oscillator(frequency, useConstantBuffer(0), trigger);\n\n  for (let i = 0; i < output[0].length; i ++) {\n    output[0][i] = oscillatorOutput[i] * amplitude[i] * envelopeOutput[i];\n    output[1][i] = output[0][i]\n  }\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: Metronome,\n  inputs: {\n    clickLength: collapse(adsrTimeSetting('Click Length', 20)),\n    downbeatFrequency: collapse(frequencySetting('Downbeat', 1500)),\n    otherBeatFrequency: collapse(frequencySetting('Other Beat', 750)),\n    frequencyDropAmount: collapse(numberSetting('Drop', { defaultValue: 0.4, min: 0, max: 1, curve: Curve.Linear })),\n    amplitude: collapse(numberSetting('Amplitude', { defaultValue: 0.75, min: 0, max: 1, curve: Curve.Linear }))\n  },\n  outputs: {\n    output: stereo('Output')\n  }\n});\n	33	2023-03-24 15:45:42.54947	Metronome.wavtool
71	Limiter	1	const constructArray = (length) => new Float64Array(Math.round(length));\nconst msToFadeMultiplier = (ms) => Math.pow(0.1, 1.0 / (ms + 1));\n\nconst Limiter = ({ input, output, release, gain, smoothing }) => {  \n  const attackSamples = useReturnedConst(msToSamples, 3, sampleRate);\n  const attackConst = useReturnedConst(msToFadeMultiplier, attackSamples)\n  const threshold = 1;\n  const appliedGain = useVal(1);\n  const smoothedTargetGain = useVal(1);\n  const delayedSignal = useDelayedStereo(input, attackSamples);\n\n  for (let i = 0; i < delayedSignal[0].length; i ++) {\n    let max = 0;\n    const releaseConst = msToFadeMultiplier(release[i]);\n    \n    for (let c = 0; c < delayedSignal.length; c ++) {\n      max = Math.max(gain[i] * Math.abs(input[c][i]), max);\n    }\n\n    let targetGain = 1;\n    \n    if (max > threshold) {\n      targetGain = threshold / max;\n    }\n\n    if (targetGain < appliedGain.current) {\n      smoothedTargetGain.current = Math.min(smoothedTargetGain.current, (targetGain - smoothing[i] * appliedGain.current) * (1/(1-smoothing[i])));\n    } else {\n      smoothedTargetGain.current = targetGain;\n    }\n\n    if (smoothedTargetGain.current < appliedGain.current) {\n      // Attacking\n      appliedGain.current = (\n        attackConst * (appliedGain.current - smoothedTargetGain.current) + \n        smoothedTargetGain.current\n      );\n\n      if (targetGain > appliedGain.current) {\n        appliedGain.current = targetGain;\n      }\n    } else {\n      // Releasing\n      appliedGain.current = (\n        releaseConst * (appliedGain.current - smoothedTargetGain.current) + \n        smoothedTargetGain.current\n      );\n    }\n\n    for (let c = 0; c < delayedSignal.length; c ++) {\n      let tempOutput = delayedSignal[c][i] * appliedGain.current * gain[i];\n      if (tempOutput > threshold) {\n        tempOutput = threshold;\n      } else if (tempOutput < -threshold) {\n        tempOutput = -threshold;\n      }\n      output[c][i] = tempOutput;\n    }\n  }\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: Limiter,\n  \n  inputs: {\n    input: stereo('In'),\n    detector: stereo('Detector'),\n    gain: dbSetting('Gain', { defaultValue: 0, min: -24, max: 24 }),\n    release: adsrTimeSetting('Release', 300),\n    smoothing: numberSetting('Smoothing', { defaultValue: 0.1, min: 0.001, max: 0.9 })\n  },\n  constants: {\n    makeupGain: booleanConstant('Makeup Gain', false)\n  },\n  outputs: {\n    output: stereo('Out', Math.round(msToSamples(3, sampleRate))),\n  },\n  hidden: {\n    gainReduction: dbSetting('Gain Reduction', {})\n  },\n  sharedArrays: {\n    detectorLevel: 1\n  },\n  layout: themed(\n    DeviceLayoutTheme.Skeuomorphic,\n    row([\n      meter('input', 4),\n      col([\n        titled.knob('gain', 'Gain', 100),\n        row([\n          titled.knob('release', 'Release', 40),\n          titled.knob('smoothing', 'Smooth', 40)\n        ])\n      ]),\n      meter('output', 4, true),\n    ])\n  )\n});\n	\N	2023-04-06 18:38:09.256875	bc24f717-88d0-4a99-b0ac-b61d4281c7c3
72	Bitcrush	70	const Bitcrush = ({ input, output, bitDepth, targetSampleRate, gain }) => {\n  const lastHeldVal = useVal(0);\n  const samplesHeld = useVal(0);\n  \n  const gained = useMonoSignal(input.length);\n  multiply(input, gain, gained);\n  clamp(gained, gained, -1, 1); \n\n  for (let i = 0; i < input.length; i ++) {\n    samplesHeld.current++\n    const segment = (sampleRate / targetSampleRate[i])\n    if (samplesHeld.current >= segment) {\n      const step = 2 * Math.pow(0.5, bitDepth[i]);\n      const val = step * ~~(gained[i] / step);\n      lastHeldVal.current = val\n      samplesHeld.current -= segment\n    }\n    output[i] = lastHeldVal.current;\n  }\n}\n\nconst Visualizer = (ctx, { bitDepth, targetSampleRate }) => {\n  ctx.fillStyle = gray200;\n  const width = ctx.canvas.width;\n  const height = ctx.canvas.height;\n  ctx.fillRect(0, 0, width, height);\n  ctx.lineWidth = 4\n  ctx.beginPath();\n  ctx.moveTo(0, height/2);\n  let heldValue = 0;\n  let sampleRateRatio = 40000 / targetSampleRate;\n  let sampledIndex = 0;\n  let crossoverX = width/2;\n  for (let x = 0; x < width; x ++) {\n    sampledIndex ++;\n    if (sampledIndex >= sampleRateRatio) {\n      sampledIndex -= sampleRateRatio\n      const originalValue = Math.sin((x / (width - 1)) * Math.PI * 2);\n      const step = 2 * Math.pow(0.5, bitDepth);\n      const newValue = step * ~~(originalValue / step);\n      if (newValue < 0 && heldValue >= 0) {\n        crossoverX = x;\n      }\n      heldValue = newValue;\n    }\n    ctx.lineTo(x, (height/2) + (heldValue * ((height/2) - 5)));\n  }\n  ctx.lineTo(width, height/2);\n  ctx.lineTo(0, height/2);\n  // ctx.stroke();\n  ctx.fillStyle = blue400;\n  ctx.fill();\n  ctx.save();\n  ctx.clip();\n  ctx.fillStyle = blue500;\n  ctx.fillRect(0, 0, width, height/2);\n  ctx.restore();\n  ctx.strokeStyle = blue400;\n  ctx.beginPath();\n  ctx.moveTo(0, height/2);\n  ctx.lineTo(crossoverX, height/2);\n  ctx.stroke();\n  ctx.strokeStyle = blue500;\n  ctx.beginPath();\n  ctx.moveTo(crossoverX, height/2);\n  ctx.lineTo(width, height/2);\n  ctx.stroke();\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: Bitcrush,\n  inputs: {\n    input: stereo('In'),\n    gain: dbSetting('Gain', { defaultValue: 0, min: -24, max: 24 }),\n    bitDepth: numberSetting('Bit Depth', { defaultValue: 6, min: 1, max: 16, curve: Curve.Log2 }),\n    targetSampleRate: numberSetting('Sample Rate', { curve: Curve.Log2, defaultValue: 8000, min: 20, max: 40000 }),\n  },\n  outputs: {\n    output: stereo('Out'),\n  },\n  layout: themed(\n    DeviceLayoutTheme.Skeuomorphic,\n    col([\n      canvas(\n        'visualizer',\n        Visualizer,\n        300,\n        150\n      ),\n      row([\n        spacer(5),\n        titled.knob('gain', 'Gain', 50),\n        spacer(90),\n        titled.knob('bitDepth', 'Bits', 50),\n        titled.knob('targetSampleRate', 'Samples', 50),\n        spacer(5),\n      ])\n    ])\n  )\n});	\N	2023-04-07 18:05:15.225629	b64cf1c9-db3b-4031-9325-2a933f9893c2
74	Drum & Drummer	1	const DrumVoice = ({\n  wavetable,\n  trigger,\n  gate,\n  velocity,\n  output,\n  basePitch,\n  modPitch,\n  wtTime,\n  pitchTime,\n  gainTime,\n  filterTime,\n}) => {\n  const exit = enterChildScope();\n\n  const gainEnvelope = useEnvelope(Curve.Log2, false);\n\n  const gain = gainEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(1 * MS_TO_SAMPLES),\n    gainTime,\n    useConstantBuffer(0),\n    gainTime\n  );\n\n  if (allEqual(gain, 0)) {\n    exit();\n    return;\n  }\n\n  const filterEnvelope = useEnvelope(Curve.Log2, false);\n  const pitchEnvelope = useEnvelope(Curve.Log2, false);\n  const wtPosEnvelope = useEnvelope(Curve.Linear, false);\n\n  const oscillator = useWavetableReader(wavetable, WavetableWarpMode.Off);\n\n  const wtPos = wtPosEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(0),\n    wtTime,\n    useConstantBuffer(0),\n    wtTime\n  );\n\n  subtract(useConstantBuffer(1), wtPos, wtPos);\n\n  const pitch = pitchEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(0),\n    pitchTime,\n    useConstantBuffer(0),\n    pitchTime\n  );\n\n  const frequency = useMonoSignal();\n  for (let i = 0; i < pitch.length; i++) {\n    frequency[i] = pitchToHz(pitch[i] * modPitch[i] + basePitch[i]);\n  }\n\n  multiply(gain, velocity, gain);\n\n  const oscillatorOutput = oscillator(\n    frequency,\n    wtPos,\n    useConstantBuffer(0),\n    useConstantBuffer(0),\n    trigger\n  );\n\n  const filterFrequency = filterEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(0),\n    filterTime,\n    useConstantBuffer(0),\n    filterTime\n  );\n\n  for (let i = 0; i < filterFrequency.length; i++) {\n    filterFrequency[i] = zeroOneToHz(filterFrequency[i] * 0.75 + 0.25);\n  }\n\n  const voiceOutput = useMonoSignal();\n\n  const filter = useBiQuadFilter();\n\n  const filterOutput = filter(\n    oscillatorOutput,\n    BiQuadFilterMode.LowPass,\n    filterFrequency,\n    useConstantBuffer(0.2),\n    useConstantBuffer(0)\n  );\n\n  multiply(filterOutput, gain, voiceOutput);\n\n  add(output[0], voiceOutput, output[0]);\n  add(output[1], voiceOutput, output[1]);\n\n  exit();\n};\n\nconst RattleVoice = (\n  trigger,\n  gate,\n  velocity,\n  output,\n  attackTime,\n  decayTime\n) => {\n  const exit = enterChildScope();\n\n  const gainEnvelope = useEnvelope(Curve.Log2, false);\n\n  const gain = gainEnvelope(\n    trigger,\n    gate,\n    attackTime,\n    decayTime,\n    useConstantBuffer(0),\n    decayTime\n  );\n\n  if (allEqual(gain, 0)) {\n    exit();\n    return;\n  }\n\n  multiply(gain, gain, gain);\n  multiply(gain, useConstantBuffer(0.35), gain);\n  multiply(gain, velocity, gain);\n\n  const noise = useOscillator(Waveform.Noise);\n  const voiceOutput = useMonoSignal();\n  const filter = useBiQuadFilter();\n  const shaper = useBiQuadFilter();\n  const filtered = filter(\n    noise(useConstantBuffer(8000)),\n    BiQuadFilterMode.BandPass,\n    useConstantBuffer(3000),\n    useConstantBuffer(0.25),\n    useConstantBuffer(0)\n  );\n  const shaped = shaper(\n    filtered,\n    BiQuadFilterMode.EQBand,\n    useConstantBuffer(4000),\n    useConstantBuffer(0.5),\n    useConstantBuffer(1.5)\n  );\n  multiply(shaped, gain, voiceOutput);\n\n  add(output[0], voiceOutput, output[0]);\n  add(output[1], voiceOutput, output[1]);\n\n  exit();\n};\n\nconst hatToneFrequencies = [953, 950, 1023, 1135, 1258].map((x) => x * 1.3);\n\nconst CymbalVoice = (settings) => {\n  const exit = enterChildScope();\n\n  const { wavetable, trigger, gate, velocity, output, attackTime, decayTime } =\n    settings;\n\n  const gainEnvelope = useEnvelope(Curve.Log2, false);\n\n  const gain = gainEnvelope(\n    trigger,\n    gate,\n    attackTime,\n    decayTime,\n    useConstantBuffer(0),\n    decayTime\n  );\n\n  if (allEqual(gain, 0)) {\n    exit();\n    return;\n  }\n\n  const zero = useConstantBuffer(0);\n  const carrierWtPosEnvelope = useEnvelope(Curve.Linear, false);\n  const modulatorWtPosEnvelope = useEnvelope(Curve.Linear, false);\n\n  multiply(gain, gain, gain);\n  multiply(gain, useConstantBuffer(0.4), gain);\n  multiply(gain, velocity, gain);\n\n  const carrierWtPos = carrierWtPosEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(0),\n    useConstantBuffer(decayTime),\n    useConstantBuffer(0),\n    useConstantBuffer(decayTime)\n  );\n  subtract(useConstantBuffer(1), carrierWtPos, carrierWtPos);\n\n  const modulatorWtPos = modulatorWtPosEnvelope(\n    trigger,\n    gate,\n    useConstantBuffer(0),\n    useConstantBuffer(decayTime),\n    useConstantBuffer(0),\n    useConstantBuffer(decayTime)\n  );\n  subtract(useConstantBuffer(1), modulatorWtPos, modulatorWtPos);\n\n  const summedTones = useMonoSignal();\n  summedTones.fill(0);\n\n  const osc1 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const osc2 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const osc3 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const osc4 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const osc5 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n\n  const osc1Freq = settings.carrierRoot;\n\n  const osc2Freq = useMonoSignal();\n  const scaledDelta1 = useMonoSignal();\n  multiply(settings.carrier1Delta, settings.carrierScale, scaledDelta1);\n  add(osc1Freq, scaledDelta1, osc2Freq);\n\n  const osc3Freq = useMonoSignal();\n  const scaledDelta2 = useMonoSignal();\n  multiply(settings.carrier2Delta, settings.carrierScale, scaledDelta2);\n  add(osc2Freq, scaledDelta2, osc3Freq);\n\n  const osc4Freq = useMonoSignal();\n  const scaledDelta3 = useMonoSignal();\n  multiply(settings.carrier3Delta, settings.carrierScale, scaledDelta3);\n  add(osc3Freq, scaledDelta3, osc4Freq);\n\n  const osc5Freq = useMonoSignal();\n  const scaledDelta4 = useMonoSignal();\n  multiply(settings.carrier4Delta, settings.carrierScale, scaledDelta4);\n  add(osc4Freq, scaledDelta4, osc5Freq);\n\n  add(\n    summedTones,\n    osc1(osc1Freq, carrierWtPos, zero, zero, trigger),\n    summedTones\n  );\n  add(\n    summedTones,\n    osc2(osc2Freq, carrierWtPos, zero, zero, trigger),\n    summedTones\n  );\n  add(\n    summedTones,\n    osc3(osc3Freq, carrierWtPos, zero, zero, trigger),\n    summedTones\n  );\n  add(\n    summedTones,\n    osc4(osc4Freq, carrierWtPos, zero, zero, trigger),\n    summedTones\n  );\n  add(\n    summedTones,\n    osc5(osc5Freq, carrierWtPos, zero, zero, trigger),\n    summedTones\n  );\n\n  const ringOsc1 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const ringOsc2 = useWavetableReader(wavetable, WavetableWarpMode.Off);\n  const ring1 = ringOsc1(\n    settings.modulator1,\n    modulatorWtPos,\n    useConstantBuffer(0),\n    useConstantBuffer(0),\n    trigger\n  );\n  const ring2 = ringOsc2(\n    settings.modulator2,\n    modulatorWtPos,\n    useConstantBuffer(0),\n    useConstantBuffer(0),\n    trigger\n  );\n\n  const bandpass1 = useBiQuadFilter();\n  const bandpass2 = useBiQuadFilter();\n  const highpass = useBiQuadFilter();\n\n  const bandpassed1 = bandpass1(\n    summedTones,\n    BiQuadFilterMode.BandPass,\n    settings.band1Freq,\n    settings.band1Res,\n    useConstantBuffer(0)\n  );\n  const bandpassed2 = bandpass2(\n    summedTones,\n    BiQuadFilterMode.BandPass,\n    settings.band2Freq,\n    settings.band2Res,\n    useConstantBuffer(0)\n  );\n\n  multiply(ring1, bandpassed1, ring1);\n  multiply(ring2, bandpassed2, ring2);\n  add(ring1, ring2, summedTones);\n\n  highpassed = highpass(\n    summedTones,\n    BiQuadFilterMode.HighPass,\n    useConstantBuffer(1000),\n    useConstantBuffer(1),\n    useConstantBuffer(0)\n  );\n  multiply(highpassed, gain, highpassed);\n  add(output[0], highpassed, output[0]);\n  add(output[1], highpassed, output[1]);\n\n  exit();\n};\n\nconst hatPitchTiming = {\n  54: {\n    cymbalDecay: 200 * MS_TO_SAMPLES,\n    cymbalAttack: 0,\n    noiseDecay: 100 * MS_TO_SAMPLES,\n    noiseAttack: 0 * MS_TO_SAMPLES,\n    noiseGain: 1,\n    cymbalGain: 1,\n  },\n  56: {\n    cymbalDecay: 100 * MS_TO_SAMPLES,\n    cymbalAttack: 4 * MS_TO_SAMPLES,\n    noiseDecay: 25 * MS_TO_SAMPLES,\n    noiseAttack: 4 * MS_TO_SAMPLES,\n    noiseGain: 0.25,\n    cymbalGain: 0.5,\n  },\n  58: {\n    cymbalDecay: 1600 * MS_TO_SAMPLES,\n    cymbalAttack: 0,\n    noiseDecay: 150 * MS_TO_SAMPLES,\n    noiseAttack: 0 * MS_TO_SAMPLES,\n    noiseGain: 1,\n    cymbalGain: 1,\n  },\n};\n\nconst voiceActivitySamples = {\n  kick: 1024,\n  snare: 1024,\n  hat: 1024,\n  hiTom: 1024,\n  lowTom: 1024,\n  midTom: 1024,\n  flrTom: 1024,\n};\n\nconst DigitalDrums = (settings) => {\n  const {\n    input,\n    output,\n    kick,\n    snare,\n    hat,\n    hiTom,\n    lowTom,\n    midTom,\n    flrTom,\n    kickWavetable,\n    snareWavetable,\n    hatWavetable,\n    tomWavetable,\n  } = settings;\n  output[0].fill(0);\n  output[1].fill(0);\n\n  hat[0].fill(0);\n  hat[1].fill(0);\n\n  hiTom[0].fill(0);\n  hiTom[1].fill(0);\n\n  lowTom[0].fill(0);\n  lowTom[1].fill(0);\n\n  midTom[0].fill(0);\n  midTom[1].fill(0);\n\n  flrTom[0].fill(0);\n  flrTom[1].fill(0);\n\n  // KICK\n  const kickVoice = useSingleVoice(input, [47,48], 2);\n  voiceActivitySamples.kick = allEqual(kickVoice.trigger, 0) ? voiceActivitySamples.kick : 1024;\n  let exit = enterChildScope();\n  if (voiceActivitySamples.kick > 0) {\n    kick[0].fill(0);\n    kick[1].fill(0);\n\n    const kickModPitch = useMonoSignal();\n    subtract(\n      settings.kickAttackPitch,\n      settings.kickResonantPitch,\n      kickModPitch\n    );\n\n    const kickPitchTime = useMonoSignal();\n    multiply(\n      kickVoice.velocity,\n      useConstantBuffer(50 * MS_TO_SAMPLES),\n      kickPitchTime\n    );\n    add(kickPitchTime, useConstantBuffer(100 * MS_TO_SAMPLES), kickPitchTime);\n\n    DrumVoice({\n      ...kickVoice,\n      wavetable: kickWavetable,\n      output: kick,\n      basePitch: settings.kickResonantPitch,\n      modPitch: kickModPitch,\n      wtTime: settings.kickWavetableScanTime,\n      pitchTime: settings.kickPitchDropTime,\n      gainTime: settings.kickAmplitudeDecayTime,\n      filterTime: settings.kickFilterDropTime,\n    });\n\n    add(output[0], kick[0], output[0]);\n    add(output[1], kick[1], output[1]);\n\n    if (allEqual(kick[0], 0) && allEqual(kick[1], 0)) {\n      voiceActivitySamples.kick -= bufferSize;\n    }\n  }\n  exit();\n\n  // SNARE\n  const snareVoice = useSingleVoice(input, [50, 51, 52], 2);\n  voiceActivitySamples.snare = allEqual(snareVoice.trigger, 0) ? voiceActivitySamples.snare : 1024;\n  exit = enterChildScope();\n\n  if (voiceActivitySamples.snare > 0) {\n    snare[0].fill(0);\n    snare[1].fill(0);\n\n    const snareSkin1 = useStereoSignal();\n    snareSkin1[0].fill(0);\n    snareSkin1[1].fill(0);\n\n    DrumVoice({\n      ...snareVoice,\n      wavetable: snareWavetable,\n      output: snareSkin1,\n      basePitch: settings.snareSkin1Pitch,\n      modPitch: useConstantBuffer(0.6),\n      wtTime: settings.snareSkin1WavetableScanTime,\n      pitchTime: useConstantBuffer(300 * MS_TO_SAMPLES),\n      gainTime: settings.snareSkin1Decay,\n      filterTime: settings.snareSkin1Decay,\n    });\n\n    multiply(snareSkin1[0], settings.snareSkin1Level, snareSkin1[0]);\n    multiply(snareSkin1[1], settings.snareSkin1Level, snareSkin1[1]);\n    add(snare[0], snareSkin1[0], snare[0]);\n    add(snare[1], snareSkin1[1], snare[1]);\n\n    const snareSkin2 = useStereoSignal();\n\n    const ringPitch = useMonoSignal();\n    add(settings.snareSkin2Offset, settings.snareSkin1Pitch, ringPitch);\n    snareSkin2[0].fill(0);\n    snareSkin2[1].fill(0);\n\n    DrumVoice({\n      ...snareVoice,\n      wavetable: snareWavetable,\n      output: snareSkin2,\n      basePitch: ringPitch,\n      modPitch: useConstantBuffer(0.75),\n      wtTime: settings.snareSkin2WavetableScanTime,\n      pitchTime: useConstantBuffer(400 * MS_TO_SAMPLES),\n      gainTime: settings.snareSkin2Decay,\n      filterTime: settings.snareSkin2Decay,\n    });\n\n    multiply(snareSkin2[0], settings.snareSkin2Level, snareSkin2[0]);\n    multiply(snareSkin2[1], settings.snareSkin2Level, snareSkin2[1]);\n\n    add(snare[0], snareSkin2[0], snare[0]);\n    add(snare[1], snareSkin2[1], snare[1]);\n\n    const snareSnares = useStereoSignal();\n    snareSnares[0].fill(0);\n    snareSnares[1].fill(0);\n    RattleVoice(\n      snareVoice.trigger,\n      snareVoice.gate,\n      snareVoice.velocity,\n      snareSnares,\n      settings.snareSnaresAttack,\n      settings.snareSnaresDecay\n    );\n\n    multiply(snareSnares[0], settings.snareSnaresLevel, snareSnares[0]);\n    multiply(snareSnares[1], settings.snareSnaresLevel, snareSnares[1]);\n\n    add(snare[0], snareSnares[0], snare[0]);\n    add(snare[1], snareSnares[1], snare[1]);\n\n    add(output[0], snare[0], output[0]);\n    add(output[1], snare[1], output[1]);\n\n    if (allEqual(snare[0], 0) && allEqual(snare[1], 0)) {\n      voiceActivitySamples.snare -= bufferSize;\n    }\n  }\n  exit();\n\n  // HAT\n\n  const hatVoice = useSingleVoice(input, [54, 56, 58], 2);\n  voiceActivitySamples.hat = allEqual(hatVoice.trigger, 0) ? voiceActivitySamples.hat : 1024;\n  exit = enterChildScope();\n\n  if (voiceActivitySamples.hat > 0) {\n    const hatCymbalDecay = useMonoSignal();\n    const hatNoiseDecay = useMonoSignal();\n    const hatNoiseAttack = useMonoSignal();\n    const hatCymbalAttack = useMonoSignal();\n    for (let i = 0; i < hatCymbalDecay.length; i++) {\n      const timingSpec = hatPitchTiming[hatVoice.pitch[i]];\n      hatCymbalDecay[i] = timingSpec?.cymbalDecay || 0;\n      hatNoiseDecay[i] = timingSpec?.noiseDecay || 0;\n      hatNoiseAttack[i] = timingSpec?.noiseAttack || 0;\n      hatCymbalAttack[i] = timingSpec?.cymbalAttack || 0;\n    }\n    const hatCymbal = useStereoSignal();\n\n    hatCymbal[0].fill(0);\n    hatCymbal[1].fill(0);\n\n    CymbalVoice({\n      ...hatVoice,\n      wavetable: hatWavetable,\n      output: hatCymbal,\n      attackTime: hatCymbalAttack,\n      decayTime: hatCymbalDecay,\n      carrierRoot: settings.hatCarrierRoot,\n      carrierScale: settings.hatCarrierScale,\n      carrier1Delta: settings.hatCarrierDelta1,\n      carrier2Delta: settings.hatCarrierDelta2,\n      carrier3Delta: settings.hatCarrierDelta3,\n      carrier4Delta: settings.hatCarrierDelta4,\n      modulator1: settings.hatModulator1Freq,\n      modulator2: settings.hatModulator2Freq,\n      band1Freq: settings.hatCarrierBandpass1Freq,\n      band2Freq: settings.hatCarrierBandpass2Freq,\n      band1Res: settings.hatCarrierBandpass1Res,\n      band2Res: settings.hatCarrierBandpass2Res,\n    });\n\n    const hatNoise = useStereoSignal();\n    hatNoise[0].fill(0);\n    hatNoise[1].fill(0);\n    RattleVoice(\n      hatVoice.trigger,\n      hatVoice.gate,\n      hatVoice.velocity,\n      hatNoise,\n      hatNoiseAttack,\n      hatNoiseDecay\n    );\n    for (let i = 0; i < hatNoise[0].length; i++) {\n      const timingSpec = hatPitchTiming[hatVoice.pitch[i]];\n      const noiseGain = timingSpec?.noiseGain || 0;\n      const cymbalGain = timingSpec?.cymbalGain || 0;\n      hatNoise[0][i] *= noiseGain;\n      hatNoise[1][i] *= noiseGain;\n      hatCymbal[0][i] *= cymbalGain;\n      hatCymbal[1][i] *= cymbalGain;\n    }\n    add(hat[0], hatNoise[0], hat[0]);\n    add(hat[1], hatNoise[1], hat[1]);\n    add(hat[0], hatCymbal[0], hat[0]);\n    add(hat[1], hatCymbal[1], hat[1]);\n\n    add(output[0], hat[0], output[0]);\n    add(output[1], hat[1], output[1]);\n\n    if (allEqual(hat[0], 0) && allEqual(hat[1], 0)) {\n      voiceActivitySamples.hat -= bufferSize;\n    }\n  }\n  exit();\n\n  const hiTomVoice = useSingleVoice(input, [59], 2);\n  const midTomVoice = useSingleVoice(input, [57], 2);\n  const lowTomVoice = useSingleVoice(input, [55], 2);\n  const flrTomVoice = useSingleVoice(input, [53], 2);\n\n  const tomPitchTime = useMonoSignal();\n  multiply(\n    hiTomVoice.velocity,\n    useConstantBuffer(50 * MS_TO_SAMPLES),\n    tomPitchTime\n  );\n  add(tomPitchTime, useConstantBuffer(100 * MS_TO_SAMPLES), tomPitchTime);\n\n  for (let i = 0; i < 4; i++) {\n    let voice, currentOutput, resonantPitchBuffer, activityKey;\n    if (i === 0) {\n      activityKey = "hiTom";\n      voice = hiTomVoice;\n      currentOutput = hiTom;\n      resonantPitchBuffer = settings.hiTomResonantPitch;\n    } else if (i === 1) {\n      activityKey = "midTom";\n      voice = midTomVoice;\n      currentOutput = midTom;\n      resonantPitchBuffer = settings.midTomResonantPitch;\n    } else if (i === 2) {\n      activityKey = "lowTom";\n      voice = lowTomVoice;\n      currentOutput = lowTom;\n      resonantPitchBuffer = settings.lowTomResonantPitch;\n    } else if (i === 3) {\n      activityKey = "flrTom";\n      voice = flrTomVoice;\n      currentOutput = flrTom;\n      resonantPitchBuffer = settings.flrTomResonantPitch;\n    }\n\n    voiceActivitySamples[activityKey] = allEqual(voice.trigger, 0) ? \n      voiceActivitySamples[activityKey] : 1024;\n\n    exit = enterChildScope();\n    if (voiceActivitySamples[activityKey] > 0) {\n      DrumVoice({\n        ...voice,\n        wavetable: tomWavetable,\n        output: currentOutput,\n        basePitch: resonantPitchBuffer,\n        modPitch: settings.tomModPitch,\n        wtTime: settings.tomWavetableScanTime,\n        pitchTime: settings.tomPitchDropTime,\n        gainTime: settings.tomAmplitudeDecayTime,\n        filterTime: settings.tomFilterDropTime,\n      });\n\n      add(output[0], currentOutput[0], output[0]);\n      add(output[1], currentOutput[1], output[1]);\n\n      if (allEqual(currentOutput[0], 0) && allEqual(currentOutput[1], 0)) {\n        voiceActivitySamples[activityKey] -= bufferSize;\n      }\n    }\n    exit();\n  }\n};\n\nconst WAVETABLES = {\n  Kick: "[encoded data, 11456 characters]",\n  Snare:\n    "[encoded data, 5380 characters]",\n  Crash:\n    "ZSTDKLUv/WBKCPVJANSRCQCCAIAng4KI7oyXjDCFlHfFZzVbSlZiWj5l+HHdelZ7V3EGXsJFWy9+IlklQzo5XkeJ5rCfy0/Um8vutqydG4Zhc2xlZVqiUAxIGUK8QPpE502pWHFh62R3YbFXGkoUPL0wIyr/KPgsKjWqQLtOwV4TcNGB6pJWom2vLLo6w63LktRy3v/oGvMT+yP/3/2G9iXpkNZBwCCoOJBRepNnVlg2TINC3DqlNQc0VTccQEpN+ltFaL5uHW4VaNNgQV2+YGRrz3lzhg2ME4jkezNsGl/NWehe/m0shE+dOrWLyBfVDNrY1+fPQMQAt+Cp9p27k1GLxoQ/gMx9OX3offN+nX/AfwCAANiBi4FWf6p7BnficbRs/mdhZIliGWN+Ztls8nVHgSqO1ZuHqZu2ncJKzXnW790U49fk2uHo2KHJArV7nW6GPHM+Zh1g1V8pY1xn5mn9aNdjnlpOTnhA+TK0J0kg1h29IH8oxDN8QE5MPlVOWu1bAVxPXaFi0202f3+VSq7LxoPcqO03+cL+Of7W9xzsBNwQyS+1f6LhkqWHQ4FJf5KAf4NMhmmHwIXmgEF56G9mZkxevlgrVjpW8FcJWlFb6VpzWDdUC08dSr1G60UDSJdMlFLEWEVe8mJZZ1psnHIaehiCW4mwjlWRNJHZjjaLUIf+g8KBuYClgA6BboFngd2AAID/HwCA1YEnhLCGO4mqi+KNvI/8kFyRrJDijjmMNYmnhpuFIocJjJGULKCJrdO6KcYLzrfRMNENzULGyr2DtASrkqEymMOONoW3e+VyvWtKZzxmfmgWbWlypXZZeNh2VnK/a11kgF1FWHZVllXlWGlf+mhGdeaDWJQRpmm4jcp22wjqPvVt/Hr/0/5B+4b1A+6J5HjYIMkntuefhIe3bnlXpEOgNC8rUydZKO8scDNKOl1AQEVBSRBNSVEeViJbfF9YYk1jpGJSYcJgb2KTZ8Vwwn09jQOdXKq9spC0sq+NpaOYz4trgc16L3jaeJ97Nn+NgvCEGIYghl+FPoQPg/qB+T8AgACEKYbahv6G2oenikWQ9JgppKGwlrwtxvLLVs3WytzFUMAAvCa6L7vSvj7EacpS0B7VLtgm2e3XiNTuzgTHhbxKr32fto0qe3dpQVrCTo9HcESDRIVGEkn0SmhLWUp0SApHwkcbTPFUMmLfcliFy5ekqN+2GcKByq3QadV52VLd/eD441zlH+Rt3+/W5MoVvKCrsJpOijZ7xG3yYWlXuU2ARLU7zDOeLT8qqSpyL5E4TkVZVOhjA3LafAuD8IPIf7R3fm0uY7BaZlX4Uz9WcltCYjZp826DcoRzLHJBb91rMmlLaMZpuW2ic5R6aoEgh/6KvIyGjM2KLYg9hW6CXwCAkoA9gtSEWoj6jPKSX5oeo7asbLZlv9PGDMykzl7OP8uExa69f7T1qhWi55oullWUWJW2mHudZaL/pfmmbqQknqaUHYkIffxxVWkVZKxiB2Wlaqdy9XtlheiNnZTzmLGa65n/ln+SJI3AhxuD+H/vfmaAloR3i8CU3Z/6qwG4zcJLy7PQrNJZ0TXN88ZLv9O26a24pE6bqZHDh7B9mHPHaaFgnFg4UuhNBUy7TAdQo1UfXeZlb29UeW+D0Y2gmO6jdq+aunbECsx90DzRJs6ax2m+wbP8qFuft5ddkhWPQ40qjDaLGYrViJuHq4Y8hleG6Yaxh1mIhYjjh0OGmIN/AIBpfp985nqHebp4mngleTN6i3vyfDR+QH8hgAyBRYIEhF6GLokfjM+O4pArkrKSoZIsknORdpAWjyKNb4rfhnyCgH1SeIRzwG+lbaVt5m9DdFZ6jYFHifGQG5iFniWkIanFrX+yxLcEvoLFRs4Q2E/iK+y59Cb73P6i/5r9Lvnw8nbrQ+PC2kPS/skNwm66+LJiq06jYZpgkEmFZ3k7bXph7VZKTiJIz0RvRNpGr0tpUnVaXWO9bGB2GoC/iQ6TqpsloxCpMK1/rxywQa8crdmplqWJoACbc5VlkFqMpIlUiD6I+Yj9ibuKwYrLidSHGoUKgih/7Hyne3l7THzmfZ8AgBmBwYGqgaWAqn7le7F4h3XhciFxdnDhcCty/3MCduF3bHmqesd7H30Rf+aBvIV1irSP75R1maSc1Z2DnGmYfZEGiI980W+zYj9WfktpQ8c+Kj7BQVlJaFQPYktxG4GkkFSf2awfuTzEY87D137goegN8H32ld3/NP6O+cHx1eYC2ajIbbYpo9uPl31abfJf2lU6T+5LtUs0TiFTQFpgY0ZusnpIiJyWKaVrs9fA8sxZ19bfVeb46vPtme848Pjv5e4U7YrqKufP4nbdIdcB0GzIuMBJuXOybaxGp+SiCZ9om7aXz5Osj3SLaYfPg+iA136jfTd9aX0Gful+vwCAh4PvhR6HCYewhTSDx3+3e1p3+nLHbtNqFWdsY8dfF1xsWPdU/1HVT9JOMk8VUWNU6FhPXkhkkGoGcYx3FH6IhMSKqJAOlt2a/J55onClIqjoqiauPrJzt9a9SMV+zQfWXd795WzsRvFG9ET1PvRU8b3syubN3xTY3c9Tx4a+bLXmq8Sh1Jbxigh+KXCNYYxSnEM8Ne0nHBweEjYKiAQlARMAUgHeBLwK8BKCHXIqqjn6Sg5eYHI8h8WbEa9CwKbOuNkx4S7lFuZ25Ong/dsl1qrPrsgywTS5wbD/pzSft5bljhOIgYJOfnh75XlheaZ5b3p+e598q32Nfjp/tH/fAIBef05+3HwRe/h4mHYFdFlxr24pbOZpAmiEZnhlzmR+ZHFkkmTZZDtltmVLZutmiWcHaE9oSWjxZ19nuGZBZlhmTmd/aTFthnKAefCBiIvtlbqggavgtYm/M8inz7zVXtqI3UHfmt+u3q/c0tlH1jnSt821yArDirwHtWissqILmLyMFoGDdWhqGWDUVr1O3EcbQl09gjlyNhs0dzKLMWExCjKeMzo2BDocP6NFuE1sV8Jiq2/2fVGNS51grQi9wssG2VDkVu3t8/z3efls+PH0K+9I55LdYNIbxj25Say+nwqUfolbgMR4yXJrbqFrWGpyasdrI25JcfN003ibfP+7DduDbRJtuzfhPIqX4sudsUgKKJBDuwOQ2+8BAVPANCz4GfAUMAVsCpB04GCc7T8E",\n\n  Tom: "[encoded data, 5528 characters]",\n};\n\nthis.constructDevice = () =>\n  constructPerBufferDevice({\n    noteInputHint: {\n      interface: NoteInterfaceType.Pads,\n      usedPitches: {\n        48: "Kick",\n        50: "Snare L",\n        51: "Snare Ghost",\n        52: "Snare R",\n        54: "Hat Closed",\n        56: "Hat Pedal",\n        58: "Hat Open",\n      },\n    },\n    implementation: DigitalDrums,\n    constants: {\n      kickWavetable: wavetableConstant("Kick Wavetable", WAVETABLES.Kick),\n      snareWavetable: wavetableConstant("Snare Wavetable", WAVETABLES.Snare),\n      hatWavetable: wavetableConstant("Hat Wavetable", WAVETABLES.Crash),\n      tomWavetable: wavetableConstant("Tom Wavetable", WAVETABLES.Tom),\n    },\n    inputs: {\n      input: midi("In"),\n\n      kickResonantPitch: numberSetting("Kick Resonant Pitch", {\n        min: 10,\n        max: 30,\n        defaultValue: 22,\n      }),\n      kickAttackPitch: numberSetting("Kick Attack Pitch", {\n        min: 40,\n        max: 55,\n        defaultValue: 43.5,\n      }),\n      kickWavetableScanTime: adsrTimeSetting("Kick Wavetable Scan Time", 220),\n      kickPitchDropTime: adsrTimeSetting("Kick Pitch Drop Time", 150),\n      kickAmplitudeDecayTime: adsrTimeSetting(\n        "Kick Amplitude Decay Time",\n        1400\n      ),\n      kickFilterDropTime: adsrTimeSetting("Kick Filter Drop Time", 92),\n\n      snareSkin1Pitch: numberSetting("Snare Skin 1 Pitch", {\n        min: 30,\n        max: 60,\n        defaultValue: 42.8,\n      }),\n      snareSkin1Level: dbSetting("Snare Skin 1 Level", {\n        defaultValue: -4,\n        max: 0,\n        min: -24,\n      }),\n      snareSkin1WavetableScanTime: adsrTimeSetting(\n        "Snare Skin 1 Wavetable Scan Time",\n        30\n      ),\n      snareSkin1Decay: adsrTimeSetting("Snare Skin 1 Decay", 230),\n\n      snareSkin2Offset: numberSetting("Snare Skin 2 Offset", {\n        min: 5,\n        max: 9,\n        defaultValue: 7.48,\n      }),\n      snareSkin2Level: dbSetting("Snare Skin 2 Level", {\n        defaultValue: -15,\n        max: -3,\n        min: -30,\n      }),\n      snareSkin2WavetableScanTime: adsrTimeSetting(\n        "Snare Skin 2 Wavetable Scan Time",\n        30\n      ),\n      snareSkin2Decay: adsrTimeSetting("Snare Skin 2 Decay", 230),\n\n      snareSnaresLevel: dbSetting("Snare Snares Level", { defaultValue: 0 }),\n      snareSnaresAttack: adsrTimeSetting("Snare Snares Attack", 17),\n      snareSnaresDecay: adsrTimeSetting("Snare Snares Decay", 190),\n\n      hatCarrierDelta1: numberSetting("Hat Carrier Delta 1", {\n        min: 0,\n        max: 100,\n        defaultValue: 1.6,\n      }),\n      hatCarrierDelta2: numberSetting("Hat Carrier Delta 2", {\n        min: 0,\n        max: 100,\n        defaultValue: 15.8,\n      }),\n      hatCarrierDelta3: numberSetting("Hat Carrier Delta 3", {\n        min: 0,\n        max: 100,\n        defaultValue: 31.8,\n      }),\n      hatCarrierDelta4: numberSetting("Hat Carrier Delta 4", {\n        min: 0,\n        max: 100,\n        defaultValue: 51.0,\n      }),\n      hatCarrierRoot: numberSetting("Hat Carrier Root", {\n        min: 900,\n        max: 1100,\n        defaultValue: 949,\n      }),\n      hatCarrierScale: numberSetting("Hat Carrier Scale", {\n        min: 1,\n        max: 5,\n        defaultValue: 3,\n      }),\n      hatModulator1Freq: numberSetting("Hat Modulator 1 Freq", {\n        min: 2000,\n        max: 8000,\n        defaultValue: 4000,\n      }),\n      hatModulator2Freq: numberSetting("Hat Modulator 2 Freq", {\n        min: 2000,\n        max: 8000,\n        defaultValue: 5000,\n      }),\n      hatCarrierBandpass1Freq: numberSetting("Hat Carrier BPF 1 Freq", {\n        min: 2000,\n        max: 8000,\n        defaultValue: 5000,\n      }),\n      hatCarrierBandpass2Freq: numberSetting("Hat Carrier BPF 2 Freq", {\n        min: 2000,\n        max: 8000,\n        defaultValue: 6500,\n      }),\n      hatCarrierBandpass1Res: numberSetting("Hat Carrier BPF 1 Res", {\n        min: 0.1,\n        max: 5,\n        defaultValue: 1,\n      }),\n      hatCarrierBandpass2Res: numberSetting("Hat Carrier BPF 2 Res", {\n        min: 0.1,\n        max: 5,\n        defaultValue: 2,\n      }),\n\n      hiTomResonantPitch: numberSetting("High Tom Resonant Pitch", {\n        min: 25,\n        max: 60,\n        defaultValue: 50,\n      }),\n      midTomResonantPitch: numberSetting("Mid Tom Resonant Pitch", {\n        min: 25,\n        max: 60,\n        defaultValue: 46,\n      }),\n      lowTomResonantPitch: numberSetting("Low Tom Resonant Pitch", {\n        min: 25,\n        max: 60,\n        defaultValue: 40,\n      }),\n      flrTomResonantPitch: numberSetting("Floor Tom Resonant Pitch", {\n        min: 25,\n        max: 60,\n        defaultValue: 34,\n      }),\n\n      tomModPitch: numberSetting("Tom Modulated Pitch", {\n        min: 0,\n        max: 12,\n        defaultValue: 6,\n      }),\n      tomWavetableScanTime: adsrTimeSetting("Tom Wavetable Scan Time", 670),\n      tomPitchDropTime: adsrTimeSetting("Tom Pitch Drop Time", 220),\n      tomAmplitudeDecayTime: adsrTimeSetting("Tom Amplitude Decay Time", 338),\n      tomFilterDropTime: adsrTimeSetting("Tom Filter Drop Time", 110),\n    },\n    outputs: {\n      output: stereo("Out"),\n      kick: collapse(stereo("Kick")),\n      snare: collapse(stereo("Snare")),\n      hat: collapse(stereo("Hat")),\n      hiTom: collapse(stereo("High Tom")),\n      midTom: collapse(stereo("Mid Tom")),\n      lowTom: collapse(stereo("Low Tom")),\n      flrTom: collapse(stereo("Floor Tom")),\n    },\n    layout: row([\n      col([\n        box(\n          col([\n            spacer(5),\n            label("Hat"),\n            spacer(5),\n            row([\n              col([\n                row([spacer(40), label("Carrier")]),\n                spacer(10),\n                row([\n                  box(label("Root"), 40),\n                  box(linear("hatCarrierRoot"), 132, 20, gray300, 4),\n                ]),\n                spacer(4),\n                row([\n                  box(label("Delta"), 40),\n                  col([\n                    row([\n                      knob("hatCarrierDelta1", { size: 30 }),\n                      spacer(4),\n                      knob("hatCarrierDelta2", { size: 30 }),\n                      spacer(4),\n                      knob("hatCarrierDelta3", { size: 30 }),\n                      spacer(4),\n                      knob("hatCarrierDelta4", { size: 30 }),\n                    ]),\n                  ]),\n                ]),\n                spacer(4),\n                row([\n                  box(label("Mult"), 40),\n                  box(linear("hatCarrierScale"), 132, 20, gray300, 4),\n                ]),\n              ]),\n              spacer(4),\n              col([\n                row([label("BPF")]),\n                spacer(4),\n                knob("hatCarrierBandpass1Freq", { size: 40 }),\n                spacer(4),\n                knob("hatCarrierBandpass2Freq", { size: 40 }),\n              ]),\n              spacer(4),\n              col([\n                row([label("Mod")]),\n                spacer(4),\n                knob("hatModulator1Freq", { size: 40 }),\n                spacer(4),\n                knob("hatModulator2Freq", { size: 40 }),\n              ]),\n              spacer(4),\n              col([\n                row([label("Pres.")]),\n                spacer(4),\n                knob("hatCarrierBandpass1Res", { size: 40 }),\n                spacer(4),\n                knob("hatCarrierBandpass2Res", { size: 40 }),\n              ]),\n              spacer(3),\n            ]),\n            spacer(5),\n          ]),\n          310,\n          "auto",\n          gray200,\n          8\n        ),\n        spacer(1),\n        box(\n          col([\n            spacer(5),\n            label("Snare"),\n            row([\n              spacer(5),\n              box(label("Skin 1"), 40, "auto"),\n              spacer(2),\n              titled.knob("snareSkin1Level", "Level", 30),\n              box(titled.knob("snareSkin1Pitch", "Pitch", 30), 60, "auto"),\n              col([\n                row([\n                  box(label("Timbre"), 50, 20),\n                  box(\n                    linear("snareSkin1WavetableScanTime"),\n                    90,\n                    20,\n                    gray300,\n                    4\n                  ),\n                ]),\n                spacer(2),\n                row([\n                  box(label("Mute"), 50, 20),\n                  box(linear("snareSkin1Decay"), 90, 20, gray300, 4),\n                ]),\n                spacer(2),\n              ]),\n              spacer(4),\n            ]),\n            row([\n              spacer(5),\n              box(label("Skin 2"), 40, "auto"),\n              spacer(2),\n              titled.knob("snareSkin2Level", "Level", 30),\n              box(titled.knob("snareSkin2Offset", "Shift", 30), 60, "auto"),\n              col([\n                row([\n                  box(label("Timbre"), 50, 20),\n                  box(\n                    linear("snareSkin2WavetableScanTime"),\n                    90,\n                    20,\n                    gray300,\n                    4\n                  ),\n                ]),\n                spacer(2),\n                row([\n                  box(label("Mute"), 50, 20),\n                  box(linear("snareSkin2Decay"), 90, 20, gray300, 4),\n                ]),\n                spacer(2),\n              ]),\n              spacer(4),\n            ]),\n            row([\n              spacer(5),\n              box(label("Snares"), 40, "auto"),\n              spacer(2),\n              titled.knob("snareSnaresLevel", "Level", 30),\n              box(spacer(0), 60, "auto"),\n              col([\n                row([\n                  box(label("Attack"), 50, 20),\n                  box(linear("snareSnaresAttack"), 90, 20, gray300, 4),\n                ]),\n                spacer(2),\n                row([\n                  box(label("Decay"), 50, 20),\n                  box(linear("snareSnaresDecay"), 90, 20, gray300, 4),\n                ]),\n                spacer(2),\n              ]),\n              spacer(4),\n            ]),\n          ]),\n          310,\n          "auto",\n          gray200,\n          8\n        ),\n        spacer(1),\n        box(\n          col([\n            spacer(5),\n            label("Kick"),\n            row([\n              titled.knob("kickResonantPitch", "Res. Pitch", 60),\n              titled.knob("kickAttackPitch", "Atk. Pitch", 60),\n              col([\n                row([\n                  box(label("Pitch"), 50, 20),\n                  box(linear("kickPitchDropTime"), 90, 20, gray300, 4),\n                ]),\n                spacer(2),\n                row([\n                  box(label("Timbre"), 50, 20),\n                  box(linear("kickWavetableScanTime"), 90, 20, gray300, 4),\n                ]),\n                spacer(2),\n                row([\n                  box(label("Clarity"), 50, 20),\n                  box(linear("kickFilterDropTime"), 90, 20, gray300, 4),\n                ]),\n                spacer(2),\n                row([\n                  box(label("Mute"), 50, 20),\n                  box(linear("kickAmplitudeDecayTime"), 90, 20, gray300, 4),\n                ]),\n              ]),\n              spacer(4),\n            ]),\n          ]),\n          310,\n          "auto",\n          gray200,\n          8\n        ),\n      ]),\n      col([\n        box(\n          col([\n            spacer(5),\n            label("Toms"),\n            row([\n              col([\n                row([\n                  knob("hiTomResonantPitch", { size: 30 }),\n                  spacer(4),\n                  knob("midTomResonantPitch", { size: 30 }),\n                  spacer(4),\n                  knob("lowTomResonantPitch", { size: 30 }),\n                  spacer(4),\n                  knob("flrTomResonantPitch", { size: 30 }),\n                ]),\n                spacer(4),\n                box(label("Resonant Pitches"), 120, 20, gray100, 4),\n              ]),\n              titled.knob("tomModPitch", "Mod. Pitch", 30),\n              col([\n                row([\n                  box(label("Pitch"), 50, 20),\n                  box(linear("tomPitchDropTime"), 90, 20, gray300, 4),\n                ]),\n                spacer(2),\n                row([\n                  box(label("Timbre"), 50, 20),\n                  box(linear("tomWavetableScanTime"), 90, 20, gray300, 4),\n                ]),\n              ]),\n              col([\n                row([\n                  box(label("Clarity"), 50, 20),\n                  box(linear("tomFilterDropTime"), 90, 20, gray300, 4),\n                ]),\n                spacer(2),\n                row([\n                  box(label("Mute"), 50, 20),\n                  box(linear("tomAmplitudeDecayTime"), 90, 20, gray300, 4),\n                ]),\n              ]),\n              spacer(4),\n            ]),\n          ]),\n          310,\n          "auto",\n          gray200,\n          8\n        ),\n      ]),\n    ]),\n    presetTags: {\n      Genre: ["Funk", "Pop", "Metal", "Drum & Bass"],\n    },\n  });\n	\N	2023-04-08 10:28:20.657784	fa951e98-7d9a-448d-8fa6-7db80e963181
76	Flanger	1	const sampleRateMultiplier = sampleRate / 1000;\n\nconst FlangerEffect = ({ input, output, mix, feedback, lfoFreq, lfoAmount, milliseconds }) => {\n  const delay = useDelay();\n  const oscillator = useOscillator(Waveform.Sine, false);\n  const oscillatorOutput = oscillator(lfoFreq);\n  const delaySamples = useMonoSignal();\n  multiply(milliseconds, useConstantBuffer(sampleRateMultiplier), delaySamples);\n  multiply(oscillatorOutput, lfoAmount, oscillatorOutput);\n  add(oscillatorOutput, delaySamples, delaySamples);\n  const delayed = delay(input, delaySamples, feedback);\n  for (let i = 0; i < input.length; i ++) {\n    output[i] = ((1 - mix[i]) * input[i]) + (mix[i] * delayed[i]);\n  }\n};\n\nthis.constructDevice = () => constructPerChannelDevice({\n  implementation: FlangerEffect,\n  inputs: {\n    input: stereo('In'),\n    mix: numberSetting('Mix', { defaultValue: 0.5, min: 0, max: 1 }),\n    lfoFreq: numberSetting('LFO Freq', { defaultValue: 1, min: 0.1, max: 10 }),\n    lfoAmount: numberSetting('LFO Amount', { defaultValue: 7.5, min: 0, max: 10 }),\n    feedback: numberSetting('Feedback', { defaultValue: 0.5, min: 0, max: 1 }),\n    milliseconds: numberSetting('milliseconds', { defaultValue: 12, min: 0, max: 25 }),\n  },\n  outputs: {\n    output: stereo('Out'),\n  }\n});\n	\N	2023-06-05 05:52:47.607904	95c16c68-cd38-4459-8616-d0c1364650ac
77	Arpeggiator	1	const getKeyMap = () => new Map();\n\nconst ARP_TYPE = ['rising', 'falling', 'bounce'];\nconst RATE = {\n  '1/64': 1 / 64,\n  '1/32': 1 / 32,\n  '1/16': 1 / 16,\n  '1/8': 1 / 8,\n  '1/6': 1 / 6,\n  '1/4': 1 / 4,\n  '1/3': 1 / 3,\n  '1/2': 1 / 2,\n};\nconst NOTE = {\n  // Length of the note in relation to the rate\n  '1/8': 1 / 8,\n  '1/6': 1 / 6,\n  '1/4': 1 / 4,\n  '1/3': 1 / 3,\n  '1/2': 1 / 2,\n  '5/8': 5 / 8,\n  '3/4': 3 / 4,\n  '7/8': 7 / 8,\n  1: 1,\n};\nconst REPEAT = {\n  '1x': 1,\n  '2x': 2,\n  '3x': 3,\n  '4x': 4,\n};\n\nfunction turnOffAllKeys(events, heldKeyVelocities) {\n  for (key of heldKeyVelocities.keys()) {\n    events.push({\n      type: 'NoteOff',\n      pitch: key,\n    });\n  }\n}\n\nfunction pushNoteOn(events, key, velocity) {\n  events.push({\n    type: 'NoteOn',\n    pitch: key,\n    velocity: velocity,\n  });\n}\n\nfunction playRisingKey(events, heldKeyVelocities, possibleKeys, keyIndex, playCount, repeatVal) {\n  if (possibleKeys.length > 0) {\n    if (playCount.current >= repeatVal) {\n      playCount.current = 0;\n      keyIndex.current++;\n      if (keyIndex.current >= possibleKeys.length) {\n        keyIndex.current = 0;\n      }\n    }\n    playCount.current++;\n    const key = possibleKeys[keyIndex.current];\n    pushNoteOn(events, key, 1);\n  }\n}\n\nfunction playFallingKey(events, heldKeyVelocities, possibleKeys, keyIndex, playCount, repeatVal) {\n  if (possibleKeys.length > 0) {\n    if (playCount.current >= repeatVal) {\n      playCount.current = 0;\n      keyIndex.current--;\n      if (keyIndex.current < 0) {\n        keyIndex.current = possibleKeys.length - 1;\n      }\n    }\n    playCount.current++;\n    const key = possibleKeys[keyIndex.current];\n    pushNoteOn(events, key, heldKeyVelocities.get(key));\n  }\n}\nfunction playBounceKey(events, heldKeyVelocities, possibleKeys, keyIndex, isRising, playCount, repeatVal) {\n  if (possibleKeys.length > 1) {\n    if (playCount.current >= repeatVal) {\n      playCount.current = 0;\n      if (isRising.current) {\n        keyIndex.current++;\n        if (keyIndex.current >= possibleKeys.length) {\n          keyIndex.current = possibleKeys.length - 2;\n          isRising.current = false;\n        }\n      } else {\n        keyIndex.current--;\n        if (keyIndex.current < 0) {\n          keyIndex.current = 1;\n          isRising.current = true;\n        }\n      }\n    }\n    playCount.current++;\n    const key = possibleKeys[keyIndex.current];\n    pushNoteOn(events, key, heldKeyVelocities.get(key));\n  } else if (possibleKeys.length === 1) {\n    pushNoteOn(events, key, heldKeyVelocities.get(possibleKeys[0]));\n  }\n}\n\nconst Arp = ({ input, output, rate, note, arpType, repeat }, { clock, bpm, playing }) => {\n  const repeatVal = ~~repeat[0];\n  const lastBeat = useVal(0);\n  const keyIndex = useVal(0); // Used for rising, falling, bounce modes\n  const isRising = useVal(true); // Used for bounce mode\n  const playCount = useVal(0); // Used for counting number of repeat a note was played\n\n  output.events = new Array(bufferSize);\n\n  const heldKeyVelocities = useReturnedConst(getKeyMap);\n  const beatsSinceFirstHeldNote = useMonoSignal();\n  const currentSampleBeats = useVal(0);\n  const previousPlaying = useVal(false);\n  let keyPressed = false;\n\n  for (let i = 0; i < input.events.length; i++) {\n    const events = input.events[i];\n    if (events) {\n      for (let j = 0; j < events.length; j++) {\n        const event = events[j];\n        if (event.type === 'NoteOn') {\n          heldKeyVelocities.set(event.pitch, event.velocity);\n          if (!playing) keyPressed = true;\n        } else if (event.type === 'NoteOff') {\n          heldKeyVelocities.delete(event.pitch);\n          if (!output.events[i]) {\n            output.events[i] = [];\n          }\n          output.events[i].push(event);\n        }\n      }\n    }\n\n    beatsSinceFirstHeldNote[i] = currentSampleBeats.current;\n\n    if (heldKeyVelocities.size === 0 && currentSampleBeats.current > 0) {\n      currentSampleBeats.current = 0;\n    } else {\n      currentSampleBeats.current += bpm[i] / 60 / sampleRate;\n    }\n  }\n\n  const effectiveClock = playing ? clock : beatsSinceFirstHeldNote;\n\n  for (let i = 0; i < effectiveClock.length; i++) {\n    // Because a beat is actually a quarter note\n    const rateVal = rate[i] * 4;\n    const noteVal = note[i] * rateVal;\n\n    const currentBeat = effectiveClock[i];\n    // Detect when the note should turn off\n    if ((keyPressed && i === 0) || currentBeat > lastBeat.current + noteVal) {\n      if (!output.events[i]) {\n        output.events[i] = [];\n      }\n      turnOffAllKeys(output.events[i], heldKeyVelocities);\n    }\n    // Detect when the note should turn on\n    if ((keyPressed && i === 0) || currentBeat > lastBeat.current + rateVal) {\n      if (!output.events[i]) {\n        output.events[i] = [];\n      }\n      const possibleKeys = Array.from(heldKeyVelocities.keys());\n      possibleKeys.sort((a, b) => a - b);\n      if (arpType === 'bounce') {\n        playBounceKey(output.events[i], heldKeyVelocities, possibleKeys, keyIndex, isRising, playCount, repeatVal);\n      } else if (arpType === 'rising') {\n        playRisingKey(output.events[i], heldKeyVelocities, possibleKeys, keyIndex, playCount, repeatVal);\n      } else if (arpType === 'falling') {\n        playFallingKey(output.events[i], heldKeyVelocities, possibleKeys, keyIndex, playCount, repeatVal);\n      }\n      lastBeat.current = currentBeat;\n    }\n  }\n\n  if (previousPlaying.current && !playing) {\n    for (key of heldKeyVelocities.keys()) {\n      output.events[0].push({\n        type: 'NoteOff',\n        pitch: key,\n      });\n      heldKeyVelocities.delete(key);\n    }\n  }\n\n  previousPlaying.current = playing;\n};\n\nconst topSpacing = 50;\nconst noteHeight = 35;\nconst noteYSpacing = 10;\nconst noteWidth = 100;\nconst Visualizer = (ctx, { note, rate, arpType, repeat }) => {\n  if (rate <= 0) {\n    return;\n  }\n  ctx.fillStyle = gray200;\n  const width = ctx.canvas.width;\n  const height = ctx.canvas.height;\n  const repeatInt = Math.round(repeat);\n  ctx.fillRect(0, 0, width, height);\n\n  let x = 10;\n  let y = topSpacing + noteHeight / 2;\n  let rateMultiplier = rate * 4;\n  let fullNoteWidth = noteWidth * rateMultiplier;\n  let renderedNoteWidth = Math.max(3.5, fullNoteWidth * note);\n  let repeatVal = 1;\n\n  ctx.strokeStyle = blue400;\n\n  // Draw header\n  ctx.fillStyle = gray150;\n  ctx.fillRect(0, 0, width, topSpacing);\n  // Draw quarter note bars\n  ctx.strokeStyle = 'rgba(255,255,255,0.4)';\n  ctx.lineWidth = 2;\n  ctx.font = '30px Arial';\n  ctx.fillStyle = 'rgba(255,255,255,0.4)';\n  ctx.textAlign = 'center';\n  ctx.fillText('♩', 35, 35);\n  for (x = 10; x < width; x += noteWidth) {\n    ctx.beginPath();\n    ctx.moveTo(x, 0);\n    ctx.lineTo(x, height);\n    ctx.stroke();\n  }\n\n  ctx.strokeStyle = 'rgba(255,255,255,0.2)';\n  ctx.lineWidth = 1;\n  // Draw note bars\n  for (x = 10; x < width; x += fullNoteWidth) {\n    ctx.beginPath();\n    ctx.moveTo(x, topSpacing);\n    ctx.lineTo(x, height);\n    ctx.stroke();\n  }\n\n  ctx.fillStyle = blue400;\n  let yPos = 0; // yPos will be used to calculate the grid position\n\n  switch (arpType) {\n    case 'falling':\n      yPos = 0;\n      for (x = 10; x < width; x += fullNoteWidth) {\n        y = yPos * (noteHeight + noteYSpacing) + topSpacing + noteYSpacing;\n        ctx.fillRect(x + 2, y + 1, renderedNoteWidth - 2, noteHeight - 2);\n        if (repeatVal >= repeatInt) {\n          yPos += 1;\n          repeatVal = 1;\n        } else {\n          repeatVal += 1;\n        }\n        if (yPos > 3) yPos = 0;\n      }\n      break;\n    case 'rising':\n      yPos = 3;\n      for (x = 10; x < width; x += fullNoteWidth) {\n        y = yPos * (noteHeight + noteYSpacing) + topSpacing + noteYSpacing;\n        ctx.fillRect(x + 2, y + 1, renderedNoteWidth - 2, noteHeight - 2);\n        if (repeatVal >= repeatInt) {\n          yPos -= 1;\n          repeatVal = 1;\n        } else {\n          repeatVal += 1;\n        }\n        if (yPos < 0) yPos = 3;\n      }\n      break;\n    case 'bounce':\n      yPos = -3;\n      for (x = 10; x < width; x += fullNoteWidth) {\n        y = Math.abs(yPos) * (noteHeight + noteYSpacing) + topSpacing + noteYSpacing;\n        ctx.fillRect(x + 2, y + 1, renderedNoteWidth - 2, noteHeight - 2);\n        if (repeatVal >= repeatInt) {\n          yPos += 1;\n          repeatVal = 1;\n        } else {\n          repeatVal += 1;\n        }\n        if (yPos > 2) yPos = -3;\n      }\n      break;\n  }\n};\n\nthis.constructDevice = () =>\n  constructPerBufferDevice({\n    implementation: Arp,\n    layout: themed(\n      DeviceLayoutTheme.Skeuomorphic,\n      col([\n        canvas('visualizer', Visualizer, 400, 120),\n        spacer(10),\n        row([\n          spacer(10),\n          col([\n            knob('rate', { showLabel: false, options: Object.values(RATE), curve: Curve.Log2 }),\n            spacer(10),\n            readout('rate', { options: Object.values(RATE), labels: Object.keys(RATE) }),\n            label('Rate'),\n          ]),\n          spacer(10),\n          col([\n            knob('note', { showLabel: false, options: Object.values(NOTE) }),\n            spacer(10),\n            readout('note', { options: Object.values(NOTE), labels: Object.keys(NOTE) }),\n            label('Note Length'),\n          ]),\n          spacer(10),\n          col([\n            knob('repeat', { showLabel: false, options: Object.values(REPEAT) }),\n            spacer(10),\n            readout('repeat', { options: Object.values(REPEAT), labels: Object.keys(REPEAT) }),\n            label('Repeat'),\n          ]),\n          spacer(40),\n          col([selector('arpType'), spacer(10), box(label('Pattern'), 100, 20)]),\n          spacer(10),\n        ]),\n        spacer(10),\n      ])\n    ),\n    constants: {\n      arpType: enumConstant('ARP Type', ARP_TYPE),\n    },\n    inputs: {\n      input: midi('In'),\n      rate: numberSetting('Rate', { defaultValue: 1 / 8, min: 1 / 64, max: 1 / 2 }),\n      note: numberSetting('Note', { defaultValue: 1, min: 1 / 8, max: 1 }),\n      repeat: numberSetting('Times', { defaultValue: 1, min: 1, max: 4 }),\n    },\n    outputs: {\n      output: midi('Out'),\n    },\n  });\n	\N	2023-06-05 17:32:21.486585	ce13b779-db69-4171-9672-72f324dd7571
79	Grid Multisampler	1	const NUMBER_OF_VOICES = 8;\n\nconst keys = [1, 2, 3, 4];\nconst vels = ['Soft', 'MedSoft', 'MedLoud', 'Loud'];\nconst velsReversed = [...vels].reverse();\n\nconst getBestSample = (sampleSpecs, pitch, velocity) => {\n  let bestSample;\n  for (let i = 0; i < sampleSpecs.length; i++) {\n    const sample = sampleSpecs[i];\n    if (!bestSample) {\n      bestSample = sample;\n    } else if (Math.abs(sample.pitch - (pitch + 3)) < Math.abs(bestSample.pitch - (pitch + 3))) {\n      bestSample = sample;\n    } else if (Math.abs(sample.velocity - velocity) < Math.abs(bestSample.velocity - velocity)) {\n      bestSample = sample;\n    }\n  }\n  return bestSample;\n};\n\nconst interpolatedRead = (buffer, index) => {\n  if (buffer.length <= 1) {\n    return 0;\n  }\n  const prev = Math.max(0, Math.floor(index));\n  const next = Math.min(buffer.length - 1, prev + 1);\n  const amountOfNextSample = index - prev;\n  const amountOfPrevSample = 1 - amountOfNextSample;\n  return buffer[prev] * amountOfPrevSample + buffer[next] * amountOfNextSample;\n};\n\nconst useMultiSampleVoice = (sampleSpecs, voice, output) => {\n  const sampleProgress = useVal(0);\n  const sampleSpec = useVal(null);\n  const playbackRate = useVal(0);\n  for (let i = 0; i < output[0].length; i++) {\n    if (voice.trigger[i] > 0.5) {\n      sampleSpec.current = getBestSample(sampleSpecs, voice.pitch[i], voice.velocity[i] * 127);\n      sampleProgress.current = 0;\n      playbackRate.current = Math.pow(2, ((voice.pitch[i] + sampleSpec.current.tune[i]) - sampleSpec.current.pitch) / 12);\n    } else if (sampleSpec.current) {\n      sampleProgress.current += playbackRate.current;\n    }\n\n    if (sampleSpec.current && sampleProgress.current < sampleSpec.current.sample.channelDataList[0].length) {\n      const cdl = sampleSpec.current.sample.channelDataList;\n      const leftChannel = cdl[0];\n      const rightChannel = cdl.length > 1 ? cdl[1] : cdl[0];\n      output[0][i] = interpolatedRead(leftChannel, sampleProgress.current);\n      output[1][i] = interpolatedRead(rightChannel, sampleProgress.current);\n    }\n  }\n};\n\nconst getSampleSpecs = (settings) => {\n  const result = [];\n  keys.forEach((k) => {\n    vels.forEach((v) => {\n      result.push({\n        pitch: settings[`sample${k}Pitch`],\n        velocity: settings[`${v}Velocity`],\n        sample: settings[`sample${k}${v}`],\n        tune: settings[`sample${k}${v}Tune`]\n      });\n    });\n  });\n  return result;\n};\n\nconst SimpleSynth = (settings) => {\n  const { input, attack, decay, sustain, release, output, gain } = settings;\n  output.forEach((c) => c.fill(0));\n  const voices = useRoundRobinVoicing(input, NUMBER_OF_VOICES);\n  const sampleSpecs = useReturnedConst(getSampleSpecs, settings);\n  for (let i = 0; i < voices.length; i++) {\n    const exit = enterChildScope();\n    const voice = voices[i];\n    const { trigger, gate } = voice;\n    const voiceGain = useEnvelope(Curve.Log2)(trigger, gate, attack, decay, sustain, release);\n\n    multiply(voiceGain, gain, voiceGain);\n\n    if (voice.alive && allEqual(voiceGain, 0)) {\n      voice.alive = false;\n    }\n\n    if (!voice.alive) {\n      exit();\n      continue;\n    }\n\n    const sampleOutput = useStereoSignal();\n    useMultiSampleVoice(sampleSpecs, voice, sampleOutput);\n\n    multiply(sampleOutput[0], voiceGain, sampleOutput[0]);\n    multiply(sampleOutput[1], voiceGain, sampleOutput[1]);\n    \n    add(output[0], sampleOutput[0], output[0]);\n    add(output[1], sampleOutput[1], output[1]);\n\n    exit();\n  }\n};\nconst constants = {};\nconst sampleTuneInputs = {};\nconst keyColumns = [];\n\nconst pianoPreset = {\n  sample1Loud: "0a0b7543-5ad5-40cf-931b-f3e549900ad9",\n  sample1MedLoud: "67f234b9-390f-4c4e-b8b8-84a037c85bfd",\n  sample1MedSoft: "3149599c-2e18-46a6-8e01-8a292a0b103d",\n  sample1Soft: "aac6d925-9e72-4aba-9d15-d0b782c33879",\n  sample2Loud: "7dff6a1d-2594-4192-b491-bcdd5324bc56",\n  sample2MedLoud: "9b37821e-2f9e-4470-9439-90fd4768543d",\n  sample2MedSoft: "59405360-922e-4adc-927a-8c20911b6e1b",\n  sample2Soft: "2489aa4b-edd2-4401-9dcb-d7e64192b9f7",\n  sample3Loud: "9810de19-6cc9-448c-a877-712c75aab6ae",\n  sample3MedLoud: "5733fa2f-335c-4f5d-beda-9ff3145a7340",\n  sample3MedSoft: "f6060279-1014-462c-9bb9-2042b8e058af",\n  sample3Soft: "c4525e69-36a9-4393-8463-9f3737db5927",\n  sample4Loud: "9269aeb1-ce57-41ec-917f-c07e1c244c6f",\n  sample4MedLoud: "8b791828-aa88-4261-9de6-db1c741a9f6c",\n  sample4MedSoft: "7157adb8-dfbd-4ddb-a53f-550f7795db9b",\n  sample4Soft: "6a6a77b0-5a8c-4a87-bbf1-65d3528b29d4",\n}\n\nkeys.forEach((k) => {\n  const currentColumn = [];\n  constants[`sample${k}Pitch`] = numberConstant(`${k} Pitch`, { defaultValue: k * 24, min: 0, max: 127 });\n  velsReversed.forEach((v) => {\n    const constantKey = `sample${k}${v}`;\n    constants[constantKey] = audioBufferConstant(`${k} (${v})`, pianoPreset[constantKey]);\n    sampleTuneInputs[`sample${k}${v}Tune`] = numberSetting(`Sample ${k} ${v} Tune`, { defaultValue: 0, max: 1, min: -1 });\n\n    currentColumn.push(\n      altKnob(\n        `sample${k}${v}Tune`,\n        'Tune',\n        titled.audioBufferPicker(`sample${k}${v}`, `${k} (${v})`)\n      )\n    );\n  });\n  currentColumn.push(titled.number(`sample${k}Pitch`));\n  keyColumns.push(col(currentColumn));\n});\n\nconst velColumn = [];\nvelsReversed.forEach((v, i) => {\n  constants[`${v}Velocity`] = numberConstant(`${v} Velocity`, {\n    defaultValue: Math.round(127 * ((vels.length - 1 - i) / (vels.length - 1))),\n    min: 0,\n    max: 127,\n  });\n  velColumn.push(box(titled.number(`${v}Velocity`, `${v} Velocity`), 'auto', 110));\n});\nkeys.slice(vels.length - 1).forEach((k) => {\n  velColumn.push(spacer(100));\n});\n\nthis.constructDevice = () =>\n  constructPerBufferDevice({\n    implementation: SimpleSynth,\n    inputs: {\n      input: midi('In'),\n      attack: adsrTimeSetting('Attack', 1),\n      decay: adsrTimeSetting('Decay', 100),\n      sustain: zeroOneSetting('Sustain', 1),\n      release: adsrTimeSetting('Release', 150),\n      gain: dbSetting('Gain', { defaultValue: -9 }),\n      ...sampleTuneInputs\n    },\n    constants,\n    layout: col([\n      row([\n        col(velColumn),\n        ...keyColumns,\n        col([\n          box(titled.knob('attack'), 'auto', 110),\n          box(titled.knob('decay'), 'auto', 110),\n          box(titled.knob('sustain'), 'auto', 110),\n          box(titled.knob('release'), 'auto', 110),\n          spacer(100)\n        ]),\n        col([\n          box(titled.knob('gain'), 'auto', 100),\n          spacer(430)\n        ]),\n      ]),\n    ]),\n    outputs: {\n      output: stereo('Out'),\n    },\n    presetTags: {\n      'Instrument': [\n        'Bass',\n        'Synth Lead',\n        'Synth Pad',\n        'Piano/Keys',\n        'Percussion',\n        'Plucked Strings',\n        'Bowed Strings',\n        'Brass',\n        'Woodwinds',\n        'Sound Effect',\n      ],\n      'Genre': [\n        'Ambient',\n        'Chill',\n        'Dubstep',\n        'EDM',\n        'Funk',\n        'Hip Hop',\n        'House',\n        'Jazz',\n        'Lo-Fi',\n        'Soundtrack',\n        'Metal',\n        'Pop',\n        'R&B',\n        'Rock',\n        'Techno',\n        'Trance',\n      ],\n      'Playback': [\n        'Monophonic',\n        'Polyphonic',\n        'Portamento',\n        'Arpeggiated',\n        'Long/Evolving',\n        'Chord',\n        'Slow Attack',\n        'Long Release'\n      ]\n    },\n  });\n	\N	2023-06-30 21:51:14.492154	84345e98-f3a7-43b2-b2f2-61bf7c475248
80	Channel Strip	1	const ChannelStripDevice = ({ input, output, send1, send2, send1Gain, send2Gain, send1Pre, send2Pre, gain, balance }) => {\n  for (let c = 0; c < input.length; c++) {\n    const inputChannel = input[c];\n    const outputChannel = output[c];\n    const send1Channel = send1[c];\n    const send2Channel = send2[c];\n\n    if (send1Pre) {\n      multiply(inputChannel, send1Gain, send1Channel);\n    }\n    if (send2Pre) {\n      multiply(inputChannel, send2Gain, send2Channel);\n    }\n    \n    for (let s = 0; s < outputChannel.length; s++) {\n      const balanceMultiplier = c === 0\n        ? Math.min(1, -1 * (balance[s] - 1))\n        : Math.min(1, balance[s] + 1);\n      outputChannel[s] = inputChannel[s] * gain[s] * balanceMultiplier;\n    }\n\n    if (!send1Pre) {\n      multiply(outputChannel, send1Gain, send1Channel);\n    }\n    if (!send2Pre) {\n      multiply(outputChannel, send2Gain, send2Channel);\n    }\n  }\n}\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: ChannelStripDevice,\n  inputs: {\n    input: stereo('In'),\n    gain: dbSetting('Gain', { defaultValue: 0, min: -Infinity, max: 12 }),\n    balance: numberSetting('Balance', { defaultValue: 0, min: -1, max: 1 }),\n    send1Gain: dbSetting('Send 1', { defaultValue: -Infinity, min: -Infinity, max: 12 }),\n    send2Gain: dbSetting('Send 2', { defaultValue: -Infinity, min: -Infinity, max: 12 }),\n  },\n  constants: {\n    send1Pre: booleanConstant('Send 1 Pre-Fader', false),\n    send2Pre: booleanConstant('Send 2 Pre-Fader', false),\n  },\n  outputs: {\n    output: stereo('Out'),\n    send1: stereo('Send 1'),\n    send2: stereo('Send 2')\n  },\n  layout: row([\n    titled.knob('gain'),\n    titled.knob('balance'),\n    titled.box(\n      'Sends',\n      row([\n        col([\n          spacer(15),\n          altCheckbox('send1Pre', 'Pre-Fader', knob('send1Gain', { size: 25 })),\n        ]),\n        col([\n          altCheckbox('send2Pre', 'Pre-Fader', knob('send2Gain', { size: 25 })),\n          spacer(15),\n        ]),\n      ])\n    )\n  ])\n});\n	34	2023-07-06 12:05:02.516619	35a2c74e-40f2-4f1c-9bad-5e207960133a
28	Sampler	1	const SampleVoice = ({ velocity, trigger, gate }, { attack, decay, sustain, release, output, buffer }) => {\n  const readIndex = useVal(0);\n  const isPlaying = useVal(false);\n\n  const channelBuffer = buffer.channelDataList[0];\n\n  const processEnvelope = useEnvelope();\n  const amplitude = processEnvelope(trigger, gate, attack, decay, sustain, release);\n  const totalAmplitude = useMonoSignal();\n  multiply(amplitude, velocity, totalAmplitude);\n  const voiceOutput = useMonoSignal();\n\n  if (channelBuffer.length <= 1) return;\n\n  voiceOutput.fill(0);\n  for (let i = 0; i < output.length; i ++) {\n    if (trigger[i] === 1) {\n      isPlaying.current = true;\n      readIndex.current = 0;\n    } else if (amplitude[i] === 0) {\n      isPlaying.current = false;\n    }\n\n    if (isPlaying.current) {\n      if (readIndex.current < channelBuffer.length) {\n        voiceOutput[i] += channelBuffer[readIndex.current];\n        readIndex.current ++;\n      }\n    }\n  }\n  multiply(voiceOutput, totalAmplitude, voiceOutput);\n  add(output, voiceOutput, output);\n}\n\nconst sampleIds = ['sample1', 'sample2', 'sample3', 'sample4', 'sample5', 'sample6', 'sample7', 'sample8'];\n\nconst getSampleData = (scope) => {\n  const results = [];\n  for (let i = 0; i < sampleIds.length; i ++) {\n    const pitches = [];\n    for (let j = 0; j < 88; j += 12) {\n      pitches.push(j + i)\n    }\n    results.push({\n      pitches,\n      scope: {\n        ...scope,\n        buffer: scope[sampleIds[i]]\n      }\n    });\n  }\n  return results;\n}\n\nconst SamplerInstrument = (scope, globals) => {\n  scope.output.fill(0);\n  const sampleData = useReturnedConst(getSampleData, scope);\n\n  for (let i = 0; i < sampleData.length; i ++) {\n    const data = sampleData[i];\n    const voice = useSingleVoice(scope.input, data.pitches);\n    SampleVoice(voice, data.scope);\n  }\n}\n\nthis.constructDevice = () => constructPerChannelDevice({\n  noteInputHint: {\n    interface: NoteInterfaceType.Pads,\n    usedPitches: {\n      48: 'Sample 1',\n      50: 'Sample 2',\n      52: 'Sample 3',\n      53: 'Sample 4',\n\n      55: 'Sample 5',\n      57: 'Sample 6',\n      59: 'Sample 7',\n      60: 'Sample 8',\n    }\n  },\n  implementation: SamplerInstrument,\n  inputs: {\n    input: midi('In'),\n    attack: adsrTimeSetting('Attack', 1),\n    decay: adsrTimeSetting('Decay', 100),\n    sustain: zeroOneSetting('Sustain', 0.25),\n    release: adsrTimeSetting('Release', 50),\n  },\n  outputs: {\n    output: stereo('Out')\n  },\n  constants: {\n    sample1: audioBufferConstant('Sample 1'),\n    sample2: audioBufferConstant('Sample 2'),\n    sample3: audioBufferConstant('Sample 3'),\n    sample4: audioBufferConstant('Sample 4'),\n\n    sample5: audioBufferConstant('Sample 5'),\n    sample6: audioBufferConstant('Sample 6'),\n    sample7: audioBufferConstant('Sample 7'),\n    sample8: audioBufferConstant('Sample 8'),\n  },\n  layout: col([\n    row([\n      audioBufferPicker('sample5'),\n      audioBufferPicker('sample6'),\n      audioBufferPicker('sample7'),\n      audioBufferPicker('sample8'),\n    ]),\n    row([\n      audioBufferPicker('sample1'),\n      audioBufferPicker('sample2'),\n      audioBufferPicker('sample3'),\n      audioBufferPicker('sample4'),\n    ]),\n    themed(DeviceLayoutTheme.Skeuomorphic, row([\n      titled.knob('attack'),\n      titled.knob('decay'),\n      titled.knob('sustain'),\n      titled.knob('release'),\n    ]))\n  ])\n});\n\n	81	2023-03-24 15:44:20.026089	31680dc3-759e-490d-85e2-0d19b033232a
78	Convolution Reverb	1	const ConvolutionReverb = ({ input, mix, output, impulseResponse }) => {\n  const leftReverb = useReverb(impulseResponse.channelDataList[0]);\n  const rightResponse = impulseResponse.channelDataList.length > 1 ? impulseResponse.channelDataList[1] : impulseResponse.channelDataList[0];\n  const rightReverb = useReverb(rightResponse);\n  const leftReverbOutput = leftReverb(input[0]);\n  const rightReverbOutput = rightReverb(input[1]);\n  multiply(leftReverbOutput, mix, leftReverbOutput);\n  multiply(rightReverbOutput, mix, rightReverbOutput);\n\n  const impulseScale = useConstantBuffer(0.0625);\n  multiply(leftReverbOutput, impulseScale, leftReverbOutput);\n  multiply(rightReverbOutput, impulseScale, rightReverbOutput);\n\n  const inverseMix = useMonoSignal();\n  const one = useConstantBuffer(1);\n  subtract(one, mix, inverseMix);\n\n  const scaledInput = useStereoSignal();\n  multiply(input[0], inverseMix, scaledInput[0]);\n  multiply(input[1], inverseMix, scaledInput[1]);\n\n  add(leftReverbOutput, scaledInput[0], output[0]);\n  add(rightReverbOutput, scaledInput[1], output[1]);\n};\n\nthis.constructDevice = () => constructPerBufferDevice({\n  implementation: ConvolutionReverb,\n  inputs: {\n    input: stereo('In'),\n    mix: zeroOneSetting('Mix', 0.5)\n  },\n  constants: {\n    impulseResponse: audioBufferConstant('Impulse Response', '5a0b3743-4356-4d99-b0c7-8371c165ffe2'),\n  },\n  outputs: {\n    output: stereo('Out'),\n  },\n  sharedArrays: {\n    inputLevel: 1,\n    reverbLevel: 1,\n  },\n  layout: themed(\n    DeviceLayoutTheme.Dark,\n    col([\n      row([\n        audioBufferPicker('impulseResponse'),\n        titled.knob('mix')\n      ])\n    ])\n  ),\n  presetTags: {\n    'Length': [\n      'Long',\n      'Short'\n    ],\n    'Environment': [\n      'Church',\n      'Concert Hall',\n      'Spring',\n      'Plate',\n      'Synthetic',\n      'Other'\n    ]\n  },\n});\n	\N	2023-06-12 20:26:24.396149	3e2e3ece-476a-42f9-b740-7da8f5e37cc8
\.


--
