{"id":28340,"date":"2022-03-23T22:02:00","date_gmt":"2022-03-23T22:02:00","guid":{"rendered":"http:\/\/Building-a-Virtual-Beat-Box-in-Redwood"},"modified":"2022-03-23T22:02:00","modified_gmt":"2022-03-23T22:02:00","slug":"building-a-virtual-beat-box-in-redwood","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/","title":{"rendered":"Building a Virtual Beat Box in Redwood"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>Sometimes you don\u2019t need to make a serious app to practice your JavaScript skills. We\u2019re going to play with a full-stack music app! It\u2019ll be a virtual beat box that you can make music with and store it in a database.<\/p>\n<h2>Setting up the app<\/h2>\n<p>We\u2019ll just jump in and start building the Redwood app because it has integrations to make it easier to set up the front-end and back-end. So in a terminal, run this command:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn create redwood-app virtual-music-box\n<\/code><\/span><\/pre>\n<p>This generates a new Redwood project with a lot of new files and directories for us and we\u2019ll be focused on the <code>web<\/code> and <code>api<\/code> directories. The <code>web<\/code> directory will hold all of the front-end code, which we\u2019ll get to a little later. The <code>api<\/code> directory contains all of the back-end code.<\/p>\n<p>To get started, let\u2019s write the back-end code.<\/p>\n<h2>Building the back-end<\/h2>\n<p>Redwood uses GraphQL to handle the back-end and Prisma to work with the database. We\u2019ll start by setting up a local Postgres instance. If you don\u2019t have Postgres installed, you can download it <a href=\"https:\/\/www.postgresql.org\/download\/\">here<\/a>.<\/p>\n<p>Now you\u2019re going to add a new file to the root of the project called <code>.env<\/code>. Inside that file, you\u2019ll need to add the connection string for your Postgres instance. It should look similar to this:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">DATABASE_URL=postgres:<span class=\"hljs-comment\">\/\/postgres:admin@localhost:5432\/mixes<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>With this connection string in place, let\u2019s move to the <code>schema.prisma<\/code> file in the <code>api &gt; db<\/code> directory. This is where you can add the models for your database. In this file, you\u2019ll see a <code>provider<\/code> with <code>sqlite<\/code> as the value. We\u2019re going to update that to <code>postgresql<\/code> like this:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">datasource db {\n  provider = <span class=\"hljs-string\">\"postgresql\"<\/span>\n  url      = env(<span class=\"hljs-string\">\"DATABASE_URL\"<\/span>)\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This is where we connect to the database using the connection string in that <code>.env<\/code> file we made. Next, we\u2019ll add a model to hold the music we make.<\/p>\n<h3>Making the model<\/h3>\n<p>You can delete the example model in <code>prisma.schema<\/code> and replace it with this:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">model Mix {\n  id     <span class=\"hljs-built_in\">String<\/span> @id @<span class=\"hljs-keyword\">default<\/span>(cuid())\n  name   <span class=\"hljs-built_in\">String<\/span>\n  sample <span class=\"hljs-built_in\">String<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We\u2019re creating a new table called <code>Mix<\/code> that has a <code>cuid<\/code> for the <code>id<\/code>, a <code>name<\/code> for the song, and the <code>sample<\/code> of notes that make up the song. Since we have the model we need in place, we can run a database migration now with this command:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw prisma migrate dev\n<\/code><\/span><\/pre>\n<p>This will create a new database on your local Postgres server and it will create a new <code>migrations<\/code> directory inside <code>api &gt; db<\/code> with the SQL to update the database.<\/p>\n<h3>Creating the GraphQL types and resolvers<\/h3>\n<p>With the database ready to go, we can start working on the GraphQL server. A cool feature that Redwood has is autogenerating the types and resolvers for the basic CRUD functionality we need to get going. We\u2019ll take advantage of this with the following command:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw g sdl mix --crud\n<\/code><\/span><\/pre>\n<p>This creates the GraphQL types and resolvers we need to create, update, delete, and read mixes we want to work with. If you take a look in <code>api &gt; src &gt; graphql<\/code>, you\u2019ll see a new file called <code>mixes.sdl.ts<\/code>. This has all of the types we need based on the model we created earlier.<\/p>\n<p>Next, take a look in <code>api &gt; src &gt; services &gt; mixes<\/code>. This holds the file for our resolvers and testing. If you open <code>mixes.ts<\/code>, you\u2019ll see all of the resolvers for create, read, update, and delete functionality already written for us.<\/p>\n<p>So now we have a fully functional back-end! That means we can switch our focus to the front-end where we actually get to make music.<\/p>\n<h2>Moving to the front-end<\/h2>\n<p>We have to set up an interface for our users to select notes to play. We\u2019ll use a grid to handle this. There are a few libraries we need to install before we start working on the component.<\/p>\n<p>In a terminal, go to the <code>web<\/code> directory and run these commands:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn add tone\nyarn add styled-components\n<\/code><\/span><\/pre>\n<p>The <code>tone<\/code> library is how we\u2019ll add sound to the browser. We\u2019ll use <code>styled-components<\/code> to help make the grid.<\/p>\n<p>Let\u2019s start by creating a new page in Redwood. In a terminal, go back to the root directory of the project and run this:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw g page mixer \/\n<\/code><\/span><\/pre>\n<p>This will create a new page for the main view of our app. It automatically updates <code>Routes.tsx<\/code> for us and if you take a look in <code>web &gt; src &gt; pages &gt; MixerPage<\/code>, you\u2019ll see the component, a Storybook story, and a unit test. Redwood generates all of this for us from that command above.<\/p>\n<h3>Adding the mixer<\/h3>\n<p>Go ahead and open <code>MixerPage.tsx<\/code> and delete everything out of it. We\u2019ll be making a completely new component. To start, we\u2019ll add all of the imports we need.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { useMutation } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@redwoodjs\/web'<\/span>\n<span class=\"hljs-keyword\">import<\/span> * <span class=\"hljs-keyword\">as<\/span> Tone <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'tone'<\/span>\n<span class=\"hljs-keyword\">import<\/span> styled <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'styled-components'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Now we can define the <code>MixerPage<\/code> component and a few styled components to get started. We\u2019ll write the code and then walk through it.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> Flex = styled.div<span class=\"hljs-string\">`\n  display: flex;\n  flex-direction: row;\n  flex-wrap: wrap;\n`<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> Square = styled.div<span class=\"hljs-string\">`\n  background-color: #ABABAB;\n  border: 2px solid #313131;\n  height: 250px;\n  width: 250px;\n`<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> MixerPage = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> notes = &#91;<span class=\"hljs-string\">'G3'<\/span>, <span class=\"hljs-string\">'A6'<\/span>, <span class=\"hljs-string\">'C9'<\/span>, <span class=\"hljs-string\">'B5'<\/span>, <span class=\"hljs-string\">'D7'<\/span>, <span class=\"hljs-string\">'F1'<\/span>, <span class=\"hljs-string\">'E8'<\/span>, <span class=\"hljs-string\">'A7'<\/span>, <span class=\"hljs-string\">'G6'<\/span>, <span class=\"hljs-string\">'B1'<\/span>, <span class=\"hljs-string\">'F4'<\/span>, <span class=\"hljs-string\">'C5'<\/span>]\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>Mixer Page<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Flex<\/span>&gt;<\/span>\n        {notes.map(note =&gt; (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Square<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{note}<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> console.log(note)} \/&gt;\n        ))}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Flex<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> console.log(mix)}&gt;Save Sounds<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/&gt;<\/span><\/span>\n  )\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> MixerPage\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>First, we make a couple of styled components. The <code>Flex<\/code> component is a flexbox we\u2019re able to make the grid shape we need for the beat box. The <code>Square<\/code> component is a colored box that represents a square in our grid.<\/p>\n<p>Then we define the <code>MixerPage<\/code> component and add the export statement at the bottom of the file. Inside the component, we add a <code>notes<\/code> array that holds the notes we want users to be able to play.<\/p>\n<p>Next, we add the return statement where we create our grid based on the number of notes in the array. We map over the <code>notes<\/code> array and add an <code>onClick<\/code> callback to work with notes. Then there\u2019s a save button that will eventually connect to the back-end and store all of the beats we make.<\/p>\n<p>If you run the app with <code>yarn rw dev<\/code>, you should see something like this in your browser.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1629992847\/e-603fc55d218a650069f5228b\/ysntk7jovn6omb6jdds4.png\" alt=\"initial beat box grid\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"920\"\/><\/p>\n<h3>Connecting the back-end to save beats<\/h3>\n<p>There\u2019s one more thing we need to add and that\u2019s the connection to the back-end. We\u2019ll add our GraphQL mutation for saving new beats right below the <code>Square<\/code> styled component.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> CREATE_MIX_MUTATION = gql`\n  mutation CreateMixMutation($input: CreateMixInput!) {\n    createMix(input: $input) {\n      id\n    }\n  }\n`\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Now we can start adding the real functionality to our grid. Inside the <code>MixerPage<\/code> component, add this code above the <code>notes<\/code> array:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> &#91;createMix] = useMutation(CREATE_MIX_MUTATION)\n<span class=\"hljs-keyword\">const<\/span> &#91;mix, setMix] = useState(&#91;])\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This gives us access to the <code>createMix<\/code> mutation defined in the GraphQL resolvers we made earlier. It also creates the <code>mix<\/code> state we\u2019ll use to store the notes on in the database.<\/p>\n<p>Now we get to do the fun thing and add the sound to our app. Below the <code>mix<\/code> state, add this line:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> mixer = <span class=\"hljs-keyword\">new<\/span> Tone.MembraneSynth().toDestination()\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This is how we use the <code>tone<\/code> library to play some kind of sound through our speakers. You can check out some of the others in <a href=\"https:\/\/tonejs.github.io\/\">their docs<\/a>.<\/p>\n<h3>Playing the notes<\/h3>\n<p>With the <code>mixer<\/code> object ready, we need to add the function that will play the notes when a user clicks on a <code>Square<\/code>.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> playNote = <span class=\"hljs-function\">(<span class=\"hljs-params\">note<\/span>) =&gt;<\/span> {\n  mixer.triggerAttackRelease(note, <span class=\"hljs-string\">\"6n\"<\/span>)\n\n  <span class=\"hljs-keyword\">const<\/span> isSet = mix.includes(note)\n\n  <span class=\"hljs-keyword\">if<\/span> (!isSet) {\n    setMix(&#91;...mix, note])\n  } <span class=\"hljs-keyword\">else<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> updateMix = mix.filter(<span class=\"hljs-function\">(<span class=\"hljs-params\">mixNote<\/span>) =&gt;<\/span> mixNote !== note)\n    setMix(updateMix)\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This <code>playNote<\/code> function takes in a string for the <code>note<\/code> value, which will be the note for the clicked <code>Square<\/code>. Then we use the <code>mixer<\/code> to actually play the sound with the <code>triggerAttackRelease<\/code> method and we pass it the <code>note<\/code> and a string for how we want the note to sound. You can play with this value and see how it changes the sound.<\/p>\n<p>Next, we do a quick check to see if the note is already in the <code>mix<\/code> state. If it is not in the <code>mix<\/code> state, we\u2019ll update the state. Otherwise, we will filter out the note from the existing state and update the <code>mix<\/code> state.<\/p>\n<p>The other function we need to make will handle saving the mixes we make.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> saveMix = <span class=\"hljs-function\">(<span class=\"hljs-params\">mix<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> input = { <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">`mix-<span class=\"hljs-subst\">${mix&#91;<span class=\"hljs-number\">0<\/span>]}<\/span>`<\/span>, <span class=\"hljs-attr\">sample<\/span>: mix.join() }\n  createMix({ <span class=\"hljs-attr\">variables<\/span>: { input } })\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This function takes the <code>mix<\/code> state and creates the <code>input<\/code> value we need to pass to the GraphQL mutation. Then we call the <code>createMix<\/code> mutation with the <code>input<\/code> value and save the mix to the database.<\/p>\n<p>Now we\u2019re ready to wrap things up by calling these functions in our elements.<\/p>\n<h3>Updating the elements<\/h3>\n<p>We need to update some props on the <code>Square<\/code> element.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Square<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{note}<\/span> <span class=\"hljs-attr\">selected<\/span>=<span class=\"hljs-string\">{mix.includes(note)}<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> playNote(note)} \/&gt;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We\u2019re using the <code>selected<\/code> prop to update the color of a square. That means we\u2019ll have to make a minor update to the <code>Square<\/code> styled component to take advantage of this prop.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> Square = styled.div<span class=\"hljs-string\">`\n  background-color: <span class=\"hljs-subst\">${props =&gt; props.selected ? <span class=\"hljs-string\">'#ABABAB'<\/span> : <span class=\"hljs-string\">'#EFEFEF'<\/span>}<\/span>;\n  border: 2px solid #313131;\n  height: 250px;\n  width: 250px;\n`<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Now when a note is selected or unselected, the color of the square will update.<\/p>\n<p>Next, we need to call the <code>saveMix<\/code> function when the <code>button<\/code> is clicked.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> saveMix(mix)}&gt;Save Sounds<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This takes the current <code>mix<\/code> state and passes it to the GraphQL mutation. If you run the app and click a few squares, you should see something like this.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1629992879\/e-603fc55d218a650069f5228b\/ewihgxrswbgy7n9veku7.png\" alt=\"selected notes in the grid\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"826\"\/><\/p>\n<p>There\u2019s one more thing we can add to take this app to the next level. We can play specific videos after the mix has been saved.<\/p>\n<h3>Adding media<\/h3>\n<p>We\u2019ll start by adding an array with links to different videos hosted in Cloudinary. Using Cloudinary just makes it easier to work with media files instead of worrying about hosting them ourselves in AWS or storing things in the database.<\/p>\n<p>So right under the <code>notes<\/code> array, add the following <code>videos<\/code> array:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> videos = &#91;<span class=\"hljs-string\">'https:\/\/res.cloudinary.com\/milecia\/video\/upload\/v1606580790\/elephant_herd.mp4'<\/span>, <span class=\"hljs-string\">'https:\/\/res.cloudinary.com\/milecia\/video\/upload\/v1606580788\/sea-turtle.mp4'<\/span>, <span class=\"hljs-string\">'https:\/\/res.cloudinary.com\/milecia\/video\/upload\/v1625835105\/test0\/tq0ejpc2uz5jakz54dsj.mp4'<\/span>, <span class=\"hljs-string\">'https:\/\/res.cloudinary.com\/milecia\/video\/upload\/v1625799334\/test0\/ebxcgjdw8fvgnj4zdson.mp4'<\/span>]\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p><strong>Feel free to make your own <a href=\"https:\/\/cloudinary.com\/users\/register\/free\">Cloudinary account<\/a> and use some videos you like!<\/strong><\/p>\n<p>This has a few video links that we\u2019ll use to display something when a mix has been saved. Now we need to create a new state to store the video source URL for when we get ready to render. You can add this below the <code>mix<\/code> state:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> &#91;video, setVideo] = useState(<span class=\"hljs-string\">''<\/span>)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We also need to add a <code>video<\/code> element below the <code>button<\/code> and its source is the <code>video<\/code> state. The <code>video<\/code> element will only display when the <code>video<\/code> state is not an empty string.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">{video !== <span class=\"hljs-string\">''<\/span> &amp;&amp;\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{video}<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">'480'<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">'360'<\/span> <span class=\"hljs-attr\">controls<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">video<\/span>&gt;<\/span><\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The last bit of code we need is to update the <code>video<\/code> state when we\u2019ve successfully saved a beat. We\u2019ll add this to the <code>saveMix<\/code> method after we call the mutation.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> randomInt = <span class=\"hljs-built_in\">Math<\/span>.floor(<span class=\"hljs-built_in\">Math<\/span>.random() * (videos.length - <span class=\"hljs-number\">1<\/span>))\nsetVideo(videos&#91;randomInt])\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This gets a random video from the array and makes it the video that plays after a successful submission. After you save a mix, you should see something like this in the browser.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1629992905\/e-603fc55d218a650069f5228b\/eevq3pih1orelocsgeod.png\" alt=\"video playing after saving the mix\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"1136\"\/><\/p>\n<h2>Finished code<\/h2>\n<p>You can take a look at the front-end code in this <a href=\"https:\/\/codesandbox.io\/s\/jolly-grass-lj5ds\">Code Sandbox<\/a> or you can check out the whole project in <a href=\"https:\/\/github.com\/flippedcoder\/blog-examples\/tree\/main\/virtual-music-box\">the <code>virtual-music-box<\/code> folder of this repo<\/a>.<\/p>\n<\/div>\n  \n  <div class=\"wp-block-cloudinary-code-sandbox \">\n    <iframe\n      src=\"https:\/\/codesandbox.io\/embed\/jolly-grass-lj5ds?theme=dark&amp;codemirror=1&amp;highlights=&amp;editorsize=50&amp;fontsize=14&amp;expanddevtools=0&amp;hidedevtools=0&amp;eslint=0&amp;forcerefresh=0&amp;hidenavigation=0&amp;initialpath=%2F&amp;module=&amp;moduleview=0&amp;previewwindow=&amp;view=&amp;runonclick=1\"\n      height=\"500\"\n      style=\"width: 100%;\"\n      title=\"jolly-grass-lj5ds\"\n      loading=\"lazy\"\n      allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n      sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n    ><\/iframe>\n  <\/div>\n\n  <div class=\"wp-block-cloudinary-markdown \"><h2>Conclusion<\/h2>\n<p>There are a lot of different ways you can play with Tone.js to improve your apps. You could use it to make things more accessible for users. You can add a different level of entertainment for users that work with your app frequently. Or you can start teaching music theory online.<\/p>\n<p>Web apps with sound give users a different experience and it\u2019s also fun to work with. Don\u2019t be afraid to try new things! You never know what you might find useful or interesting.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28341,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[389,134,370,177,246,371,303],"class_list":["post-28340","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-audio","tag-guest-post","tag-image","tag-javascript","tag-react","tag-under-review","tag-video"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Building a Virtual Beat Box in Redwood<\/title>\n<meta name=\"description\" content=\"Most of the time the only media types that get attention in web development are videos and images. We&#039;re going to make a small app that will let users work with audio in the browser and make their own beats!\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a Virtual Beat Box in Redwood\" \/>\n<meta property=\"og:description\" content=\"Most of the time the only media types that get attention in web development are videos and images. We&#039;re going to make a small app that will let users work with audio in the browser and make their own beats!\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-03-23T22:02:00+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924759\/Web_Assets\/blog\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec.jpg?_i=AA\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Building a Virtual Beat Box in Redwood\",\"datePublished\":\"2022-03-23T22:02:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/\"},\"wordCount\":7,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924759\/Web_Assets\/blog\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec.jpg?_i=AA\",\"keywords\":[\"Audio\",\"Guest Post\",\"Image\",\"Javascript\",\"React\",\"Under Review\",\"Video\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/\",\"name\":\"Building a Virtual Beat Box in Redwood\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924759\/Web_Assets\/blog\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec.jpg?_i=AA\",\"datePublished\":\"2022-03-23T22:02:00+00:00\",\"description\":\"Most of the time the only media types that get attention in web development are videos and images. We're going to make a small app that will let users work with audio in the browser and make their own beats!\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924759\/Web_Assets\/blog\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924759\/Web_Assets\/blog\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec.jpg?_i=AA\",\"width\":7952,\"height\":5304},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building a Virtual Beat Box in Redwood\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Building a Virtual Beat Box in Redwood","description":"Most of the time the only media types that get attention in web development are videos and images. We're going to make a small app that will let users work with audio in the browser and make their own beats!","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/","og_locale":"en_US","og_type":"article","og_title":"Building a Virtual Beat Box in Redwood","og_description":"Most of the time the only media types that get attention in web development are videos and images. We're going to make a small app that will let users work with audio in the browser and make their own beats!","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-03-23T22:02:00+00:00","twitter_card":"summary_large_image","twitter_image":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924759\/Web_Assets\/blog\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec.jpg?_i=AA","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/"},"author":{"name":"","@id":""},"headline":"Building a Virtual Beat Box in Redwood","datePublished":"2022-03-23T22:02:00+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/"},"wordCount":7,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924759\/Web_Assets\/blog\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec.jpg?_i=AA","keywords":["Audio","Guest Post","Image","Javascript","React","Under Review","Video"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/","name":"Building a Virtual Beat Box in Redwood","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924759\/Web_Assets\/blog\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec.jpg?_i=AA","datePublished":"2022-03-23T22:02:00+00:00","description":"Most of the time the only media types that get attention in web development are videos and images. We're going to make a small app that will let users work with audio in the browser and make their own beats!","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924759\/Web_Assets\/blog\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924759\/Web_Assets\/blog\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec.jpg?_i=AA","width":7952,"height":5304},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-virtual-beat-box-in-redwood\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Building a Virtual Beat Box in Redwood"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924759\/Web_Assets\/blog\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec\/64a0e14dd2b637e55c2784dcc52a2885c64920b4-7952x5304-1_28341abeec.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28340","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28340"}],"version-history":[{"count":0,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28340\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28341"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28340"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28340"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28340"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}