#17 – Destiny Kanno and Joe Simpson on Why They Started BlackPress

On the podcast today we have Destiny Kanno and Joe Simpson.

Destiny and Joe are the key figures behind a new initiative called BlackPress.

The goal of BlackPress is to bring more creators of Black African descent into the WordPress community, and also provide a community space for those already there to connect, learn from, and support each other.

It’s still in the early stages and they are trying to grow with a dedicated Slack channel and regular Meetups. It’s intended to be a safe space for people who share their vision to work together and try to figure out what such a community might look like.

We talk on the podcast about the journeys they have both had in the WordPress space and why they decided to collaborate on this project. What does the WordPress community need to be mindful of when WordCamps, Meetups and other events are organised? Is it true that WordPress events are open to all people equally? What are the aspirations that they have for their own events in the future, and how can you join the BlackPress community?

It’s a really thought provoking discussion, and shines a light on a very important issue.

BlackPress Slack channel

BlackPress Meetup To Host Meet and Greet Mixer on January 27

Transcript

[00:00:00] Nathan Wrigley: welcome to the Jukebox podcast from WP Tavern. My name is Nathan Wrigley. Jukebox is a podcast which is dedicated to all things WordPress. The people, the events, the plugins, the blocks, the themes, and in this case, diversity within the community. If you’d like to subscribe to the podcast, you can do that by searching for WP Tavern in your podcast, player of choice.

All by going to WP tavern.com forward slash feed forward slash podcast. And you can copy that URL into most podcast players. If you have a topic that you’d like us to feature on the podcast, I’m keen to hear from you, and hopefully get you or your idea featured on the show. Head over to WP Tavern.com forward slash contact forward slash jukebox and use the contact form there.

So on the podcast today, we have Destiny Kanno and Joe Simpson. Destiny and Joe are key figures behind a new initiative called BlackPress. The goal of BlackPress is to bring more creators of black African descent into the WordPress community, and also provide a community space for those already there to connect, learn from and support each other.

It’s still in the early stages, and they’re trying to grow with a dedicated Slack channel and regular meetups. It’s intended to be a safe space for people who share their vision to work together and to try to figure out what such a community might look like. We talk on the podcast about the journeys they’ve both had in the WordPress space and why they decided to collaborate on this project.

What does the WordPress community need to be mindful of when WordCamps, meetups, and other events organized? Is it true that WordPress events are open to all people equally? What are the aspirations that they have for their own events in the future? And how can you join the BlackPress community? It’s a really thought provoking discussion and shines a light on a very important issue.

If you’re interested in finding out more, you can find all the links in the show notes by heading over to WP Tavern.com forward slash podcast, where you’ll find all the episodes. And so without further delay, I bring you Destiny Kanno and Joe Simpson. I am joined on the podcast by two people Destiny Kanno, and Joe Simpson. Hello.

[00:03:00] Joe Simpson: Hey Nathan.

[00:03:01] Destiny Kanno: Hey there.

[00:03:02] Nathan Wrigley: Can I take it one at a time, if that’s all right, I’ll start with you. Destiny. We’re going to have a conversation today about a subject, which is probably, let’s say it will be broad and deep and probably some different opinions will arise.

But, first of all, I think it might be nice to have a little bit of a background story of both of you. Briefly or in long form, whichever you prefer. Destiny, could you give us a little bit of background? How come you’re on a WordPress podcast? What’s your relationship to the WordPress community?

[00:03:31] Destiny Kanno: Yes. Thank you for the intro, Nathan. So I started my journey with WordPress in 2017. I was working at a small Japanese digital agency, and at the time we were working on a few WordPress sites, but, to be fully honest, I didn’t really get that involved with them until I switched over to Automattic as a dot com happiness engineer.

And that’s where my real push to learn WordPress better started off. And I did that for about two years, moved over to the WordPress VIP sector, working with large enterprise clients using WordPress at scale. And now I currently sit in our dot org division with a lovely community member people, who are working in that area, as a developer relations as advocate.

[00:04:19] Nathan Wrigley: Thank you very much. Indeed. Could I ask the same question of you, Joe? Briefly what’s your relationship with WordPress?

[00:04:25] Joe Simpson: Oh sure. Nathan, my journey is two-pronged, it’s a before and an after. Way back around 2011, I worked for a public transit agency here in Los Angeles, and our CSS goddess and lead developer both left our company within a month of each other. And I inherited a WordPress site. I was a graphic designer turned HTML novice, and I had to quickly learn WordPress.

And that was my first experience with the WordPress community. We were able to move our site over to WordPress VIP. And from that point I learned about WordPress in a disciplined way. How to commit code properly and things like that. So speed ahead to 2017, I had a heart event. 200% blocked artery, and I had to take a leave of absence from work.

During that 90 day period, I decided to do the things that I really loved. And one of the things that had such a positive impact on me was WordPress. And I attended my first meetup. I decided later instead of driving into Los Angeles, and getting home at 11 o’clock at night after work, I wanted to start a community locally here in Santa Clarita, where there wasn’t one.

And then also that became a WordCamp. And we hosted three WordCamps here in Santa Clarita. So I’ve been in the community as a builder, as a community rep, and now I’m on the team. I’m a team rep for the WordPress, make WordPress accessibility team. And now that leads me to this initiative as well. I’m trying to give back in different ways and in effect, WordPress from a different angle now.

[00:05:50] Nathan Wrigley: Okay, I’m going to be referring during the course of this, to an article which was written by Justin Tadlock over on WP Tavern. It was called BlackPress meetup to host meet and greet mixer on the 27th of January. That has now been and gone. This episode will probably be going out in March 2022. And so I want to know from both of you and it doesn’t matter who begins first or whether you cross talk, that’s absolutely fine, we can work that out later. I want to know what this event was all about. Why did you two decide to create the BlackPress meet up, and also slack channel? What was the primary thinking? What were you trying to accomplish?

[00:06:30] Destiny Kanno: That’s a great question, Nathan. So from my side of things, at Automattic, I co-founded our black employee resource group which we call Cocomatic, in April, 2020. And so for me, working with that group, we wanted to figure out a way to also bring this community to folks of black African descent, into the WordPress community space as well.

So I worked alongside another colleague of mine, Neisha Sweet, to come together, the community side and work on what we now call BlackPress, and bring that to life. In our initial initiatives or ideas that we had in our first talks were about students really, you know, bringing more students of color, specifically black students across the globe. So that’s why we say black African descent, into WordPress and giving them that kind of mentorship, sponsorship, earlier in the process so that they can really get involved, whether it’s in high school or ideally in college. And we also had talks about targeting HBCU, historically black universities and colleges as well.

And so that really was in my mind, and Joe, please add on, you know, like the birth of why we wanted to have this initiative at the beginning,

[00:07:48] Joe Simpson: And I’m on the other side of the fence where I’m fully in the community and I’m an open source advocate, and I use it as part of my day to day. And my community activities brought me to this initiative as well. Someone on the foundation side, reached out to me and mentioned that there were some folks interested in this.

And for me, the reason that I got involved with WordPress and I decided to take the stage as a speaker, was that I didn’t see anybody that looked like me on stage and I decided to propose a speech and then it went from there. So, this was a natural fit for me because I have a nephew that attended an HBCU and just the initiative of reaching a larger global audience and getting more people involved in how we can impact WordPress and its direction was very exciting for me.

[00:08:35] Nathan Wrigley: Can I just ask you to tell me what an HBCU is? Because that acronym is not one that’s in my vocabulary?

[00:08:43] Destiny Kanno: Yeah. So that’s a historically black university and college. It’s mainly a, I believe, an American thing, stemming off of, segregation in our past here. We have a few members of BlackPress that went to those schools, and so that was a target area for us of bringing more folks into WordPress.

[00:09:01] Nathan Wrigley: Okay, thank you. That just cleared that up for me, that’s great. So you had your first event back in January. I’d just like to figure out what your thoughts were on how that went. So if we could just open up your little history book, how did the meeting go? Was It well attended? Did you manage to make some decisions about what it is that you’re going to be doing in the future? You can take this in whichever direction you like.

[00:09:23] Destiny Kanno: It had a great meetup attendee, yes. We had about 24 folks say, yes, we want to do this. But I think with all community events, about half is going to come if you’re lucky more than that. And so we were just grateful when the day came, when we had about nine folks, including Joe and myself attend.

And the discussions we had were definitely about, where we’re coming from in terms of our WordPress journey. Where we’d like to improve ourselves. And also what we’d like to get out of the BlackPress group. And that included, you know, how to make your site better, to how to be a better developer. Talking about design. There’s a lot of things that folks want to share and learn about together within this group is what we discovered during the mixer.

[00:10:07] Joe Simpson: Yeah, what was really awesome too, is that not just in the meetup space did people express an interest in BlackPress? There was Slack channel that was set up. That channel has grown incredibly fast, as the word is getting out, and as meetups are basically word of mouth, then you have to get the word out and then eventually more people would join.

We had an online Slack discussion that was well attended as well. So it’s really exciting that people are coming to our space to talk and discuss these issues.

[00:10:34] Nathan Wrigley: Do you have a fixed agenda about what it is that you would like to achieve? Or are you still very much in the stages of trying to work out with those people who show up what it is that you want the project to become, who it is that you want to take the responsibility for certain things? Do you have any things solidified yet, or is it still very much figuring it all out?

[00:10:56] Destiny Kanno: We are still very much hoping to get to this large HBCU college style, WordCamp style event. And so we’re looking for folks to step up to organize, but we also understand that that takes a bit of onboarding, and resource sharing in order to make that happen.

[00:11:15] Joe Simpson: And to add onto that, I know in general, as a meetup organizer myself, these things take time. But it’s always based on the energy, enthusiasm of people that come to the project. So, as we get more and more people on board, and, for example, the Slack channel is a great place where everyday someone’s sharing information, starting the discussion, so those type of people hopefully will take a larger role in the group and the group will just grow organically.

[00:11:44] Nathan Wrigley: Could you drop the URL? I will put it in the show notes, but perhaps one of you could say it out loud so that anybody listening, perhaps sitting in a cafe or something could put their phone down and go and actually visit that site. What’s the URL for the Slack channel?

[00:11:59] Joe Simpson: Oh the Slack channel. It’s a super long gobbledygook URL. We could probably create a short link for it.

[00:12:05] Nathan Wrigley: Okay, I’ll add it into the show notes. So I would add that anybody listening to this, just head over to the show notes on WP Tavern, and look for the podcast section there, and from there you can find those things.

I was interested to hear you Joe say, we’re going to be straying into an area which maybe is uncomfortable. I don’t know. See how we go. You said that you were concerned in the past that you’d attended events and there was nobody that looked like you. Two things that I want to ask from that. First thing, what does that feel like? That’s my first question. And secondly, has any of that changed in the recent past?

[00:12:43] Joe Simpson: I would say to clarify the statement, it was that I didn’t see people that look like me on stage. Now in the WordPress community, there were people that look like me in the audience, at all the WordCamps that I had attended, but again, seeing someone on stage and talking about WordPress, I hadn’t seen that.

And ironically, the first event that I was accepted to, directly before me, Joe Howard presented. So that very day was the first day I saw someone. So from that point forward, it’s become more and more diverse in the WordPress space, diversity has been a big point of emphasis over the past three or four years.

So I would say it’s changed dramatically. And I know for me, in the communities that I’m involved with, that’s always an emphasis on what we do. The WordCamps that we’ve started, our organizing team has been incredibly diverse. Our WordCamps have all been at least 50 50 or a larger percentage in the other direction in terms of being a well-represented and diverse in terms of gender, as well as race.

So it’s just something that happens naturally in the spaces that I try to associate with, or that I flow within. So it’s just natural. And to me, I think you mentioned that some uncomfortability or things of that nature. For me, my experiences in the WordPress space haven’t been that way.

And I want to make sure that whenever someone’s involved with initiatives I’m involved in, it should always be open to anyone and everyone, and make sure that you feel that you can express yourself and get your ideas across.

[00:14:11] Nathan Wrigley: Destiny. Could I ask you the same question? Now, obviously it doesn’t reflect back on something that you said earlier, but I’m guessing that there are moments in the past where you’ve looked around you at events and just pondered those questions. What is the mix of people in this arena? And am I going to feel comfortable? Should I be one of the speakers? Do I feel comfortable here? Are people represented equally?

[00:14:31] Destiny Kanno: Yeah. So I have to out myself. I have not been to a WordCamp yet, say that with a single tear in my eye. I’ll be there at WordCamp EU. So in terms of like a WordCamp style event I don’t have any experience that I can speak to, but in terms of other events, and just growing into myself as well. Definitely, it’s true what Joe is saying. Just seeing someone that looks like you in speaker or organizing position, is a bit of a position of power, right? In those positions where you’re showing leadership or you’re showing what you could become. A person in this space that’s respected and well listened to is important to folks of all kinds.

But when I’m going to events, I’m looking for that, but I’m also in the community space, seeing how people treat me. Am I able to speak my mind respectfully, of course, in a way that’s true to myself? Am I able to come in and be vulnerable too and not be laughed at because my experience in the space, or perhaps the fact that I am the only black woman in the room.

So not just being on stage, but also just how we can navigate the spaces are two things that I look for in an inclusive kind of event.

[00:15:40] Nathan Wrigley: I’d really like for this podcast to possibly make people think about something that they haven’t really been confronted with before, and so I’m going to return to the question if it’s okay with you the actuality. Now, in Destiny’s case, it’s not going to be a WordPress event, and Joe, you may like to cast your mind to a WordPress event, or it may be something completely different because I just want to put myself in your shoes for a moment where you attend an event and you look around the room and it’s pretty clear that the representation is not what it could be. Make of that what you will. I want to know what that actually feels like. I apologize if that’s a question, which is something that you don’t wish to answer, but I’m going to ask it anyway.

[00:16:27] Destiny Kanno: Joe, do you want to go first on that one?

[00:16:29] Joe Simpson: Oh, sure. For me, and I shared this story over time. When I first got into the community. That enthusiasm or that level of excitement that you often feel when you’re around someone that’s new to WordPress or open source or something that they’re really interested in. I needed to harness it.

And initially, my local WordCamp here, I want it to be involved and I had all these ideas and I was sketching out things. And if you know me, I go overboard. And I’d proposed all these ideas and how I wanted to get involved, and unfortunately I wasn’t in that events clique, so to speak, and no fault of theirs, every event is based on relationships between the organizers and the volunteers and things of that nature. I was just some new guy. What that rejection or what that inability to have my ideas heard, what that spawned was, what I’ve done here locally in Santa Clarita or what I’ve tried to do in everything that I’ve been involved in is just to bring my voice and have my voice heard and be in the room.

So I think it’s motivation for me. I think anytime we get a rejection or a no, for me, it just drives me to get involved and become part of that discussion. That’s just me personally, and I can’t speak for others. But I feel like in terms of being a diverse person in a non-diverse environment and having people consider other ideas. I’ll give you a different example. In WordCamps, for example, sometimes they ask for very specific things in terms of the speakers that they’re looking for. For example, there was an event in Canada and they only wanted, I think Gutenberg topics. And so that limits the type of presentations they’ll get. It may be exciting in terms of pushing something that’s hot in the WordPress community through, but just naturally it excludes people that don’t have expertise in Gutenberg.

So just having people consider all different options or all different voices is something that isn’t done. And I think these type of initiatives bring that to light and that can only help in the long term.

[00:18:31] Nathan Wrigley: Thank you. Destiny, same question.

[00:18:33] Destiny Kanno: Yeah, so I’ll draw on a bit of life experience for this one. So I lived in Japan for about five years and that is a country where, you really need to speak Japanese to get around for the most part. So, this is a feeling I think anyone can have, right. This feeling of either alienation because you haven’t been able to learn the language and bond with the people in a way that is respectful to their culture, but also you understand their culture. And so you can either, blend in, walk the walk and talk the talk, or you can feel alienation. And that’s one like from the ex-pat kind of spectrum. I think there’s a more simpler example that I can give, like taking us all, that we’ve all been. I shouldn’t say all, a lot of people have been schoolchildren, and I think most of us know what it’s like to be maybe the new kid at school or start your first day.

I like to think that most kids, they don’t come in thinking about white supremacist culture and all of these things. We’re just going to school, we, we’re listening to our emo music or whatever that is. You know, othering people based on appearances, and in that scenario, I think is, an indoctrination process, based on systemic societal issues, because as a child, you’re just thinking, oh my gosh anyone like me, I might like some grunge music that no one’s ever heard of. And in that case you’re trying to fit in any way you can. And maybe you’ll find the people that accept you no matter what, or maybe you won’t. But in that scenario, I personally think a lot of the time, it’s not about, skin tone, it’s about, cliques and whether or not you’re going to be accepted. So, definitely not WordPress examples, but I feel like these are two things that I think anyone can wrap their mind around, this feeling of being othered in some way.

[00:20:16] Nathan Wrigley: We talk often in the WordPress space that it’s basically open to anybody. The idea being that it’s open source software. If you have a desire to be part of the community, you can because everybody is available. Maybe there’s some discussion to be had here because I have a feeling having spoken to a variety of people over the last few years about this, that may not be entirely true.

In other words, being able to show up and contribute, may be contingent upon a whole bunch of other factors. So for example, your ability to have the time available, your ability to have your employer second you into work. Your ability to be in a situation where you are, I don’t know, stable enough and have enough support to allow you to have the free time and so on and so forth. I wonder if there’s anything to say about that, about the wider goals of open source software in this case, WordPress, and whether or not you feel that WordPress as a whole needs to think more about this notion of well, anybody can contribute because it’s totally open to everybody. Is that true?

[00:21:25] Destiny Kanno: I think it’s true in that anyone can contribute, like full-stop right. Anyone with a computer and internet access. So those are two requirements actually, can contribute. Other than that, there’s other factors like how well will you be recognized for your contributions. Who is getting recognized?

So contribution only goes so far, but participation, sponsorship, being seen in leadership roles, there’s a whole other area where, maybe some folks cannot break through as easily.

[00:21:59] Nathan Wrigley: Joe, any thoughts on that?

[00:22:01] Joe Simpson: Well, I mean, I do think it’s possible and I think it happens everyday. It’s just a matter of making those opportunities available, to as wide an audience as possible. I know I participated in some diverse speaking workshops, and I know in terms of the events that I’ve done, we’ve made a conscious effort to include new speakers, to include people who haven’t presented before, and we offer them training, or give them a stage. They can come and speak at our meetup to practice and to get that sense of ownership or that sense of buy-in. And I think that’s for me, finding out where those avenues for inclusion are and making sure that they’re available and known to people that are coming into the community. Those voices that are diverse, making sure that they know that there are opportunities to do that. And the example that I gave earlier in terms of that frustration that I had when I first wanted to get involved with a WorkCamp event, I didn’t know that there was a call for speakers or call for organizing. I mean, I didn’t know of any of the paths or processes that were already in place because I was new and sometimes things in the open source community, you have to figure out on your own.

So for me, with an initiative like this, what I’m hoping is that this collaboration with the folks on the Automattic side and our community, is this new collaboration can open up so many more doors for people to get involved in making sure that if you want to speak, hey, you should do this. Hey, this event’s coming up. They need organizers. This is how you help organize. And being that conduit will only make things more diverse and more inclusive. I think a lot of times those avenues are, they expect that you know those, but most people don’t. So what I always try to do in our community events and hopefully as we move forward with this one is to make sure that people know the path or the opportunities that are there and how they can take full advantage of them.

[00:23:47] Nathan Wrigley: Let’s do a little bit of blue sky thinking. Let’s imagine that the BlackPress project is a runaway success. And a couple of years from now, you know, there’s a real up swelling of support and the community has grown really strongly, and all of the issues that you’ve dealt with today have been looked at and addressed, and you’ve come up with some novel solutions for different problems. Do you just want to give us some ideas about the kind of things that you would, Joe, you just touched on a few of them, but the concrete things that you want to have happen. In other words, how many people would you like to be a part of that community? What kind of job titles, for want of a better word, would you like to have? What kind of documents would you like to have produced or videos or whatever it may be? What concrete things would you wish could happen, let’s say two years from now?

[00:24:37] Destiny Kanno: I would say in an ideal world two years from now, we would have folks from every region of the world, involved in the project, maybe even some, EU, EMEA APAC channels as well. Just connecting us all, in WordPress, in the BlackPress community. That would be an amazing goal. All of that network essentially, folks that anywhere in the world you go, there’s going to be a WordPresser that you can connect with or see at a WordCamp and enjoy time with.

So that’s definitely in my mind, a amazing goal for us to have. BlackPress Isn’t trying to replace the larger WordPress community, it’s not trying to become like a make Slack in that way, and at least in my mind, Joe definitely correct me if I’m wrong here.

[00:25:19] Joe Simpson: I totally agree.

[00:25:20] Destiny Kanno: Yeah, like we, but we still want to have a space where, as you got to Nathan, you know, we can help better circulate opportunities amongst each other. We can help better sponsor, mentor, prepare each other for organizer roles, for speaking engagements. So that is definitely a goal that we have, and are iterating on, even right now.

[00:25:40] Joe Simpson: Destiny summed it up pretty well. I mean, just having that place where we can help the next generation of contributors and creators in the WordPress space would be awesome. Wouldn’t it be cool if BlackPress had panel at WordCamp US or WordCamp Europe? Or if we do an HBCU, a WordCamp style event. What if we did a world event? In the past three years? What I’ve really been excited about is, the pandemic has forced us all inward, unfortunately, but the offshoot of that is that all of our meetups are now global. We have people that come in from Europe that come in from Africa that come in from Australia, growing it into Asia.

Those kinds of things are really exciting because, not all of us can travel to a WordCamp event, even when everything was in person. I may not be able to fly to WordCamp US in Nashville, for example, because of work commitments or personal commitment, the virtual spaces allowed us to grow in a different direction.

So I’m excited that once things go back to a more in-person or even a hybrid situation, those people that we’ve reached out to all over the world will really have an impact on WordPress.

[00:26:45] Nathan Wrigley: Thank you very much, indeed. So that was our blue sky thinking moment. We were casting the net two years into the future and imagining what great things are possible. Let’s go back to today, rein it in a little bit. You’ve obviously beginning something. The enthusiasm is often great at the beginning and then reality of what needs to be done sets in and you realize that, okay, this is where we’re at we haven’t reached these grand goals. Right now. If I could wave a magic wand and give you the things that you need to happen in the next couple of months, what would those look like? Are you looking for new membership? Is it all about the people? Is it trying to find people from different locales?

What do you really want to do in the next couple of months to kickstart this whole enterprise?

[00:27:29] Destiny Kanno: Well, we had our first meetup as we noted at the beginning of this call, last month. So to keep momentum we’re continuing with our meetups. So we actually have another one tomorrow from 5:00 PM Pacific time. And that is with Allie Nimmons from Underrepresented in Tech, discussing that. Next month we have one coming up to discuss 5.9 and full site editing.

So that is keeping up with our meetups is one way that we’re hoping to definitely increase membership and grow our community. I don’t think I said before, but when we started in January this year kicking off these events in our Slack, we only had about 17 ish people. Now we’re at 50 in our BlackPress meetup as well. We are hitting 50 plus as well. We are seeing people being excited about this initiative and we’re growing and that’s definitely something that’s going to help us toward our goals of creating these events as well.

[00:28:23] Nathan Wrigley: Yeah, so more boots on the ground, more people, fresh ideas. And I guess that’s the nice thing about growing a community at the beginning is that you have a vague idea of where the direction of travel would like to go, but then the people arrive and maybe hijack the enterprise and say, actually, what about this idea? Oh, that’s curious, I didn’t think about trying that out. Let’s give that a go. So yeah, boots on the ground would be good. Joe, anything to add to that?

[00:28:48] Joe Simpson: I was just going to say, it’s all of the things that Destiny mentioned. Just the more events you have, the more people will come. The more you get the word out, the more the group will grow. So to me, just being consistent and the events that Destiny’s mentioned will gain exposure being on podcasts, such as this one, more people will hear the message. Will hear what we’re trying to do, and hopefully it’ll come and it’ll just grow that way.

[00:29:09] Nathan Wrigley: Okay, one other question. It occurs to me that you obviously want the membership to grow. BlackPress we described that you’d like it to be swelling into the future. Is there any kind of walled garden around this? Is there any part of the community that you are going to welcome more? Or are you going for full inclusivity? Everybody is welcome. Please all come and give us your ideas.

[00:29:37] Destiny Kanno: Yes, this is a great question. And we get it all the time. Who is allowed, quote, unquote, allowed to join BlackPress meetup or Slack, and this meetup, and community, we treat it like any other community in WordPress. It’s welcome to all. However folks of non-black African descent, who we’re calling allies, just be aware that this is a space where, they should be uplifting black voices or looking for ways to advocate for, or sponsor or share ideas in a way that isn’t taking away from the experiences of the community here that we built. So it’s a safe space for everyone, but just keep it in mind.

Taking up space as a real thing, right? And this is a space where you’re able to be, but also with an understanding that it is for folks of black African descent. We want to be able to collaborate and share ideas, but we also want to ensure that this is a safe space for the target community as well.

It’s really all of us that need to be doing this work to get to a better place where we don’t even, meet these kinds of offshoot communities. I think ideally everyone wants to be in that space where, everywhere in WordPress, it’s yeah, we acknowledge that you are different, and we love that you’re different. And we’re going to talk about your perspective and respect that, and we’re all going to be great, but we’re still in a space where differences are seen as something to other or not be included.

And so that’s, I think where this idea of that’s your space, so I don’t want to be in there bothering you, maybe comes from in my head.

[00:31:11] Joe Simpson: We mentioned it at the top when we kicked off the conversation, a lot of times we’ll have people that will call out the fact that they’re not black or is it okay to ask this question a certain way? But I think that level of consciousness is what moves the discussion forward.

So in my circles, there’s an understanding. If I generate the level respect that I should, that shouldn’t be an issue. And you’re always welcome to participate and to ask certain questions. So the story that I love to share is when I went away to college and, you know, oftentimes during spring breaks or things of that nature, you may go to your roommates, family’s home or vice versa. And I grew up in in an inner city environment. And one of my roommates, when he came with me and we were headed back to college campus, he said, wow, I didn’t know it was like that. No one really noticed I was white. And to me, that’s sort of the spirit of what I try to do in the WordPress space, it’s like, respect me, but there aren’t any walls or any barriers to what you and I, as friends or as colleagues, or as co-organizers should have. We should both be able to speak freely.

And within the WordPress space where there are codes of conduct for meetups and, there’s guidelines and rules for all this stuff that is the WordPress way or the WordPress community. As long as we’re in that environment, our conversations will flow and be organic. So, it should never be an issue.

[00:32:30] Destiny Kanno: I was just going to add just thinking about reflecting on that question a bit more, I feel like it’s as a black person navigating the world, we have to ask that question like all the time. Can I join? Will I be accepted? Will people listen to me? So yeah, like when I think about that question now, I’m just thinking like, that’s part of, trying to become a community. I don’t know, you know, into a community. Yeah. I think it all just boils down to respect. Are you entering that space respectfully or are you trying to, make it all about you?

[00:33:01] Nathan Wrigley: Thank you so much. Just before we wind it up, what would be the best place to interact with the pair of you? Maybe we’ll take it one at a time, but maybe one of you wants to deal with the usual thing, maybe there’s an email address or a Twitter handle, or let’s see if we can dig out that Slack group address or whatever.

But where’s the best place to contact you about BlackPress more generally. And then if you wish to tell us specifically, where could we find you personally best online, that might be helpful too. So let’s start with Destiny.

[00:33:34] Destiny Kanno: Yeah, I think the make WordPress Slack is a good space to start if you’re there. And if you’re not, you should join. And if not, you can also find me on Twitter, the destiny wp.

[00:33:46] Joe Simpson: Yeah, for the meetup group, you can find us on meetup.com slash BlackPress dash meetup. And you can find me anywhere in the WordPress space. Joe Simpson Junior on WordPress.org. Twitter, LinkedIn, it’s always Joe Simpson, Jr. So, I welcome anyone that wants to reach out and find out more about BlackPress.

[00:34:05] Nathan Wrigley: That was really interesting. Thank you Destiny and Joe, thank you for coming onto the podcast today. I really appreciate it.

[00:34:12] Destiny Kanno: Thank you, Nathan.

[00:34:13] Joe Simpson: Yeah. Thanks for having us.

A Guide To Audio Visualization With JavaScript And GSAP (Part 2)

Last week in Part 1, I explained how the idea about how to record audio input from users and then moved on to the visualization. After all, without any visualization, any type of audio recording UI isn’t very engaging, is it? Today, we’ll be diving into more details in terms of adding features and any sort of extra touches you like!

We’ll be covering the following:

Please note that in order to see the demos in action, you’ll need to open and test directly them on the CodePen website.

Pausing A Recording

Pausing a recording doesn’t take much code at all.

// Pause a recorder
recorder.pause()
// Resume a recording
recorder.resume()

In fact, the trickiest part about integrating recording is designing your UI. Once you’ve got a UI design, it’ll likely be more about the changes you need for that.

Also, pausing a recording doesn’t pause our animation. So we need to make sure we stop that too. We only want to add new bars whilst we are recording. To determine what state the recorder is in, we can use the state property mentioned earlier. Here’s our updated toggle functionality:

const RECORDING = recorder.state === 'recording'
// Pause or resume recorder based on state.
TOGGLE.style.setProperty('--active', RECORDING ? 0 : 1)
timeline[RECORDING ? 'pause' : 'play']()
recorder[RECORDING ? 'pause' : 'resume']()

And here’s how we can determine whether to add new bars in the reporter or not.

REPORT = () => {
  if (recorder && recorder.state === 'recording') {

Challenge: Could we also remove the report function from gsap.ticker for extra performance? Try it out.

For our demo, we’ve changed it so the record button becomes a pause button. And once a recording has begun, a stop button appears. This will need some extra code to handle that state. React is a good fit for this but we can lean into the recorder.state value.

See the Pen 15. Pausing a Recording by Jhey.

Padding Out The Visuals

Next, we need to pad out our visuals. What do we mean by that? Well, we go from an empty canvas to bars streaming across. It’s quite a contrast and it would be nice to have the canvas filled with zero volume bars on start. There is no reason we can’t do this either based on how we are generating our bars. Let’s start by creating a padding function, padTimeline:

// Move BAR_DURATION out of scope so it’s a shared variable.
const BAR_DURATION =
  CANVAS.width / ((CONFIG.barWidth + CONFIG.barGap) * CONFIG.fps)

const padTimeline = () => {
  // Doesn’t matter if we have more bars than width. We will shift them over to the correct spot
  const padCount = Math.floor(CANVAS.width / CONFIG.barWidth)

  for (let p = 0; p < padCount; p++) {
    const BAR = {
      x: CANVAS.width + CONFIG.barWidth / 2,
      // Note the volume is 0
      size: gsap.utils.mapRange(
        0,
        100,
        CANVAS.height * CONFIG.barMinHeight,
        CANVAS.height * CONFIG.barMaxHeight
      )(volume),
    }
    // Add to bars Array
    BARS.push(BAR)
    // Add the bar animation to the timeline
    // The actual pixels per second is (1 / fps * shift) * fps
    // if we have 50fps, the bar needs to have moved bar width before the next comes in
    // 1/50 = 4 === 50 * 4 = 200
    timeline.to(
      BAR,
      {
        x: `-=${CANVAS.width + CONFIG.barWidth}`,
        ease: 'none',
        duration: BAR_DURATION,
      },
      BARS.length * (1 / CONFIG.fps)
    )
    }
  // Sets the timeline to the correct spot for being added to
  timeline.totalTime(timeline.totalDuration() - BAR_DURATION)
}

The trick here is to add new bars and then set the playhead of the timeline to where the bars fill the canvas. At the point of padding the timeline, we know that we only have padding bars so totalDuration can be used.

timeline.totalTime(timeline.totalDuration() - BAR_DURATION)

Notice how that functionality is very like what we do inside the REPORT function? We have a good opportunity to refactor here. Let’s create a new function named addBar. This adds a new bar based on the passed volume.

const addBar = (volume = 0) => {
  const BAR = {
    x: CANVAS.width + CONFIG.barWidth / 2,
    size: gsap.utils.mapRange(
      0,
      100,
      CANVAS.height * CONFIG.barMinHeight,
      CANVAS.height * CONFIG.barMaxHeight
    )(volume),
  }
  BARS.push(BAR)
  timeline.to(
    BAR,
    {
      x: `-=${CANVAS.width + CONFIG.barWidth}`,
      ease: 'none',
      duration: BAR_DURATION,
    },
    BARS.length * (1 / CONFIG.fps)
  )
}

Now our padTimeline and REPORT functions can make use of this:

const padTimeline = () => {
  const padCount = Math.floor(CANVAS.width / CONFIG.barWidth)
  for (let p = 0; p < padCount; p++) {
    addBar()
  }
  timeline.totalTime(timeline.totalDuration() - BAR_DURATION)
}

REPORT = () => {
  if (recorder && recorder.state === 'recording') {
    ANALYSER.getByteFrequencyData(DATA_ARR)
    const VOLUME = Math.floor((Math.max(...DATA_ARR) / 255) * 100)
    addBar(VOLUME)
  }
  if (recorder || visualizing) {
    drawBars()
  }
}

Now, on load, we can do an initial rendering by invoking padTimeline followed by drawBars.

padTimeline()
drawBars()

Putting it all together and that’s another neat feature!

See the Pen 16. Padding out the Timeline by Jhey.

How We Finish

Do you want to pull the component down or do a rewind, maybe a rollout? How does this affect performance? A rollout is easier. But a rewind is trickier and might have perf hits.

Finishing The Recording

You can finish up your recording any way you like. You could stop the animation and leave it there. Or, if we stop the animation we could roll back the animation to the start. This is often used in various UI/UX designs. And the GSAP API gives us a neat way to do this. Instead of clearing our timeline on stop, we can move this into where we start a recording to reset the timeline. But, once we’ve finished a recording, let’s keep the animation around so we can use it.

STOP.addEventListener('click', () => {
  if (recorder) recorder.stop()
  AUDIO_CONTEXT.close()
  // Pause the timeline
  timeline.pause()
  // Animate the playhead back to the START_POINT
  gsap.to(timeline, {
    totalTime: START_POINT,
    onComplete: () => {
      gsap.ticker.remove(REPORT)
    }
  })
})

In this code, we tween the totalTime back to where we set the playhead in padTimeline. That means we needed to create a variable for sharing that.

let START_POINT

And we can set that within padTimeline.

const padTimeline = () => {
  const padCount = Math.floor(CANVAS.width / CONFIG.barWidth)
  for (let p = 0; p < padCount; p++) {
    addBar()
  }
  START_POINT = timeline.totalDuration() - BAR_DURATION
  // Sets the timeline to the correct spot for being added to
  timeline.totalTime(START_POINT)
}

We can clear the timeline inside the RECORD function when we start a recording:

// Reset the timeline
timeline.clear()

And this gives us what is becoming a pretty neat audio visualizer:

See the Pen 17. Rewinding on Stop by Jhey.

Scrubbing The Values On Playback

Now we’ve got our recording, we can play it back with the <audio> element. But, we’d like to sync our visualization with the recording playback. With GSAP’s API, this is far easier than you might expect.

const SCRUB = (time = 0, trackTime = 0) => {
  gsap.to(timeline, {
    totalTime: time,
    onComplete: () => {
      AUDIO.currentTime = trackTime
      gsap.ticker.remove(REPORT)
    },
  })
}
const UPDATE = e => {
  switch (e.type) {
    case 'play':
      timeline.totalTime(AUDIO.currentTime + START_POINT)
      timeline.play()
      gsap.ticker.add(REPORT)
      break
    case 'seeking':
    case 'seeked':
      timeline.totalTime(AUDIO.currentTime + START_POINT)
      break
    case 'pause':
      timeline.pause()
      break
    case 'ended':
      timeline.pause()
      SCRUB(START_POINT)
      break
  }
}

// Set up AUDIO scrubbing
['play', 'seeking', 'seeked', 'pause', 'ended']
  .forEach(event => AUDIO.addEventListener(event, UPDATE))

We’ve refactored the functionality that we use when stopping to scrub the timeline. And then it’s a case of listening for different events on the <audio> element. Each event requires updating the timeline playhead. We can add and remove REPORT to the ticker based on when we play and stop audio. But, this does have an edge case. If you seek after the audio has "ended", the visualization won’t render updates. And that’s because we remove REPORT from the ticker in SCRUB. You could opt to not remove REPORT at all until a new recording begins or you move to another state in your app. It’s a matter of monitoring performance and what feels right.

The fun part here though is that if you make a recording, you can scrub the visualization when you seek 😎

See the Pen 18. Syncing with Playback by Jhey.

At this point, you know everything you need to know. But, if you want to learn about some extra things, keep reading.

Audio Playback From Other Sources

One thing we haven’t looked at is how you visualize audio from a source other than an input device. For example, an mp3 file. And this brings up an interesting challenge or problem to think about.

Let’s consider a demo where we have an audio file URL and we want to visualize it with our visualization. We can explicitly set our AUDIO element’s src before visualizing.

AUDIO.src = 'https://assets.codepen.io/605876/lobo-loco-spencer-bluegrass-blues.mp3'
// NOTE:: This is required in some circumstances due to CORS
AUDIO.crossOrigin = 'anonymous'

We no longer need to think about setting up the recorder or using the controls to trigger it. As we have an audio element, we can set the visualization to hook into the source direct.

const ANALYSE = stream => {
  if (AUDIO_CONTEXT) return
  AUDIO_CONTEXT = new AudioContext()
  ANALYSER = AUDIO_CONTEXT.createAnalyser()
  ANALYSER.fftSize = CONFIG.fft
  const DATA_ARR = new Uint8Array(ANALYSER.frequencyBinCount)
  SOURCE = AUDIO_CONTEXT.createMediaElementSource(AUDIO)
  const GAIN_NODE = AUDIO_CONTEXT.createGain()
  GAIN_NODE.value = 0.5
  GAIN_NODE.connect(AUDIO_CONTEXT.destination)
  SOURCE.connect(GAIN_NODE)
  SOURCE.connect(ANALYSER)

  // Reset the bars and pad them out...
  if (BARS && BARS.length > 0) {
    BARS.length = 0
    padTimeline()
  }

  REPORT = () => {
    if (!AUDIO.paused || !played) {
      ANALYSER.getByteFrequencyData(DATA_ARR)
      const VOLUME = Math.floor((Math.max(...DATA_ARR) / 255) * 100)
      addBar(VOLUME)
      drawBars()  
    }
  }
  gsap.ticker.add(REPORT)
}

By doing this we can connect our AudioContext to the audio element. We do this using createMediaElementSource(AUDIO) instead of createMediaStreamSource(stream). And then the audio elements' controls will trigger data getting passed to the analyzer. In fact, we only need to create the AudioContext once. Because once we’ve played the audio track, we aren’t working with a different audio track after. Hence, the return if AUDIO_CONTEXT exists.

if (AUDIO_CONTEXT) return

One other thing to note here. Because we’re hooking up the audio element to an AudioContext, we need to create a gain node. This gain node allows us to hear the audio track.

SOURCE = AUDIO_CONTEXT.createMediaElementSource(AUDIO)
const GAIN_NODE = AUDIO_CONTEXT.createGain()
GAIN_NODE.value = 0.5
GAIN_NODE.connect(AUDIO_CONTEXT.destination)
SOURCE.connect(GAIN_NODE)
SOURCE.connect(ANALYSER)

Things do change a little in how we process events on the audio element. In fact, for this example, when we’ve finished the audio track, we can remove REPORT from the ticker. But, we add drawBars to the ticker. This is so if we play the track again or seek, etc. we don’t need to process the audio again. This is like how we handled playback of the visualization with the recorder.

This update happens inside the SCRUB function and you can also see a new played variable. We can use this to determine whether we’ve processed the whole audio track.

const SCRUB = (time = 0, trackTime = 0) => {
  gsap.to(timeline, {
    totalTime: time,
    onComplete: () => {
      AUDIO.currentTime = trackTime
      if (!played) {
        played = true
        gsap.ticker.remove(REPORT)
        gsap.ticker.add(drawBars) 
      }
    },
  })
}

Why not add and remove drawBars from the ticker based on what we are doing with the audio element? We could do this. We could look at gsap.ticker._listeners and determine if drawBars was already used or not. We may choose to add and remove when playing and pausing. And then we could also add and remove when seeking and finishing seeking. The trick would be making sure we don’t add to the ticker too much when "seeking". And this would be where to check if drawBars was already part of the ticker. This is of course dependent on performance though. Is that optimization going to be worth the minimal performance gain? It comes down to what exactly your app needs to do. For this demo, once the audio gets processed, we are switching out the ticker function. That’s because we don’t need to process the audio again. And leaving drawBars running in the ticker shows no performance hit.

const UPDATE = e => {
  switch (e.type) {
    case 'play':
      if (!played) ANALYSE()
      timeline.totalTime(AUDIO.currentTime + START_POINT)
      timeline.play()
      break
    case 'seeking':
    case 'seeked':
      timeline.totalTime(AUDIO.currentTime + START_POINT)
      break 
    case 'pause':
      timeline.pause()
      break
    case 'ended':
      timeline.pause()
      SCRUB(START_POINT)
      break
  }
}

Our switch statement is much the same but we instead only ANALYSE if we haven’t played the track.

And this gives us the following demo:

See the Pen 19. Processing Audio Files by Jhey.

Challenge: Could you extend this demo to support different tracks? Try extending the demo to accept different audio tracks. Maybe a user can select from dropdown or input a URL.

This demo leads to an interesting problem that arose when working on "Record a Call" for Kent C. Dodds. It’s not one I’d needed to deal with before. In the demo above, start playing the audio and seek forwards in the track before it finishes playing. Seeking forwards breaks the visualization because we are skipping ahead of time. And that means we are skipping processing certain parts of the audio.

How can you resolve this? It’s an interesting problem. You want to build the animation timeline before you play audio. But, to build it, you need to play through the audio first. Could you disable "seeking" until you’ve played through once? You could. At this point, you might start drifting into the world of custom audio players. Definitely out of scope for this article. In a real-world scenario, you may be able to put server-side processing in place. This might give you a way to get the audio data ahead of time before playing it.

For Kent’s “Record a Call”, we can take a different approach. We are processing the audio as it’s recorded. And each bar gets represented by a number. If we create an Array of numbers representing the bars, we already have the data to build the animation. When a recording gets submitted, the data can go with it. Then when we make a request for audio, we can get that data too and build the visualization before playback.

We can use the addBar function we defined earlier whilst looping over the audio data Array.

// Given an audio data Array example
const AUDIO_DATA = [100, 85, 43, 12, 36, 0, 0, 0, 200, 220, 130]

const buildViz = DATA => {
  DATA.forEach(bar => addBar(bar))
}

buildViz(AUDIO_DATA)

Building our visualizations without processing the audio again is a great performance win.

Consider this extended demo of our recording demo. Each recording gets stored in localStorage. And we can load a recording to play it. But, instead of processing the audio to play it, we build a new bars animation and set the audio element src.

Note: You need to scroll down to see stored recordings in the <details> and <summary> element.

See the Pen 20. Saved Recordings ✨ by Jhey.

What needs to happen here to store and playback recordings? Well, it doesn’t take much as we have the bulk of functionality in place. And as we’ve refactored things into mini utility functions, this makes things easier.

Let’s start with how we are going to store the recordings in localStorage. On page load, we are going to hydrate a variable from localStorage. If there is nothing to hydrate with, we can instantiate the variable with a default value.

const INITIAL_VALUE = { recordings: []}
const KEY = 'recordings'
const RECORDINGS = window.localStorage.getItem(KEY)
  ? JSON.parse(window.localStorage.getItem(KEY))
  : INITIAL_VALUE

Now. It’s worth noting that this guide isn’t about building a polished app or experience. It’s giving you the tools you need to go off and make it your own. I’m saying this because some of the UX, you might want to put in place in a different way.

To save a recording, we can trigger a save in the ondataavailable method we’ve been using.

recorder.ondataavailable = (event) => {
  // All the other handling code
  // save the recording
  if (confirm('Save Recording?')) {
    saveRecording()
  }
}

The process of saving a recording requires a little "trick". We need to convert our AudioBlob into a String. That way, we can save it to localStorage. To do this, we use the FileReader API to convert the AudioBlob to a data URL. Once we have that, we can create a new recording object and persist it to localStorage.

const saveRecording = async () => {
  const reader = new FileReader()
  reader.onload = e => {
    const audioSafe = e.target.result
    const timestamp = new Date()
    RECORDINGS.recordings = [
      ...RECORDINGS.recordings,
      {
        audioBlob: audioSafe,
        metadata: METADATA,
        name: timestamp.toUTCString(),
        id: timestamp.getTime(),
      },
    ]
    window.localStorage.setItem(KEY, JSON.stringify(RECORDINGS))
    renderRecordings()
    alert('Recording Saved')  
  }
  await reader.readAsDataURL(AUDIO_BLOB)
}

You could create whatever type of format you like here. For ease, I’m using the time as an id. The metadata field is the Array we use to build our animation. The timestamp field is being used like a "name". But, you could do something like name it based on the number of recordings. Then you could update the UI to allow users to rename the recording. Or you could even do it through the save step with window.prompt.

In fact, this demo uses the window.prompt UX so you can see how that would work.

See the Pen 21. Prompt for Recording Name 🚀 by Jhey.

You may be wondering what renderRecordings does. Well, as we aren’t using a framework, we need to update the UI ourselves. We call this function on load and every time we save or delete a recording.

The idea is that if we have recordings, we loop over them and create list items to append to our recordings list. If we don’t have any recordings, we are showing a message to the user.

For each recording, we create two buttons. One for playing the recording, and another for deleting the recording.

const renderRecordings = () => {
  RECORDINGS_LIST.innerHTML = ''
  if (RECORDINGS.recordings.length > 0) {
    RECORDINGS_MESSAGE.style.display = 'none'
    RECORDINGS.recordings.reverse().forEach(recording => {
      const LI = document.createElement('li')
      LI.className = 'recordings__recording'
      LI.innerHTML = `<span>${recording.name}</span>`
      const BTN = document.createElement('button')
      BTN.className = 'recordings__play recordings__control'
      BTN.setAttribute('data-recording', recording.id)
      BTN.title = 'Play Recording'
      BTN.innerHTML = SVGIconMarkup
      LI.appendChild(BTN)
      const DEL = document.createElement('button')
      DEL.setAttribute('data-recording', recording.id)
      DEL.className = 'recordings__delete recordings__control'
      DEL.title = 'Delete Recording'
      DEL.innerHTML = SVGIconMarkup
      LI.appendChild(DEL)
      BTN.addEventListener('click', playRecording)
      DEL.addEventListener('click', deleteRecording)
      RECORDINGS_LIST.appendChild(LI)
    })
  } else {
    RECORDINGS_MESSAGE.style.display = 'block'
  }
}

Playing a recording means setting the AUDIO element src and generating the visualization. Before playing a recording or when we delete a recording, we reset the state of the UI with a reset function.

const reset = () => {
  AUDIO.src = null
  BARS.length = 0
  gsap.ticker.remove(REPORT)
  REPORT = null
  timeline.clear()
  padTimeline()
  drawBars()
}

const playRecording = (e) => {
  const idToPlay = parseInt(e.currentTarget.getAttribute('data-recording'), 10)
  reset()
  const RECORDING = RECORDINGS.recordings.filter(recording => recording.id === idToPlay)[0]
  RECORDING.metadata.forEach(bar => addBar(bar))
  REPORT = drawBars
  AUDIO.src = RECORDING.audioBlob
  AUDIO.play()
}

The actual method of playback and showing the visualization comes down to four lines.

RECORDING.metadata.forEach(bar => addBar(bar))
REPORT = drawBars
AUDIO.src = RECORDING.audioBlob
AUDIO.play()
  1. Loop over the metadata Array to build the timeline.
  2. Set the REPORT function to drawBars.
  3. Set the AUDIO src.
  4. Play the audio which in turn triggers the animation timeline to play.

Challenge: Can you spot any edge cases in the UX? Any issues that could arise? What if we are recording and then choose to play a recording? Could we disable controls when we are in recording mode?

To delete a recording, we use the same reset method but we set a new value in localStorage for our recordings. Once we’ve done that, we need to renderRecordings to show the updates.

const deleteRecording = (e) => {
  if (confirm('Delete Recording?')) {
    const idToDelete = parseInt(e.currentTarget.getAttribute('data-recording'), 10)
    RECORDINGS.recordings = [...RECORDINGS.recordings.filter(recording => recording.id !== idToDelete)]
    window.localStorage.setItem(KEY, JSON.stringify(RECORDINGS))
    reset()
    renderRecordings()    
  }
}

At this stage, we have a functional voice recording app using localStorage. It makes for an interesting start point that you could take and add new features to and improve the UX. For example, how about making it possible for users to download their recordings? Or what if different users could have different themes for their visualization? You could store colors, speeds, etc. against recordings. Then it would be a case of updating the canvas properties and catering for changes in the timeline build. For “Record a Call”, we supported different canvas colors based on the team a user was part of.

This demo supports downloading tracks in the .ogg format.

See the Pen 22. Downloadable Recordings 🚀 by Jhey.

But you could take this app in various directions. Here are some ideas to think about:

  • Reskin the app with a different "look and feel"
  • Support different playback speeds
  • Create different visualization styles. For example, how might you record the metadata for a waveform type visualization?
  • Displaying the recordings count to the user
  • Improve the UX catching edge cases such as the recording to playback scenario from earlier.
  • Allow users to choose their audio input device
  • Take your visualizations 3D with something like ThreeJS
  • Limit the recording time. This would be vital in a real-world app. You would want to limit the size of the data getting sent to the server. It would also enforce recordings to be concise.
  • Currently, downloading would only work in .ogg format. We can’t encode the recording to mp3 in the browser. But you could use serverless with ffmpeg to convert the audio to .mp3 for the user and return it.
Turning This Into A React Application

Well. If you’ve got this far, you have all the fundamentals you need to go off and have fun making audio recording apps. But, I did mention at the top of the article, we used React on the project. As our demos have got more complex and we’ve introduced "state", using a framework makes sense. We aren’t going to go deep into building the app out with React but we can touch on how to approach it. If you’re new to React, check out this "Getting Started Guide" that will get you in a good place.

The main problem we face when switching over to React land is thinking about how we break things up. There isn’t a right or wrong. And then that introduces the problem of how we pass data around via props, etc. For this app, it’s not too tricky. We could have a component for the visualization, the audio playback, and recordings. And then we may opt to wrap them all inside one parent component.

For passing data around and accessing things in the DOM, React.useRef plays an important part. This is “a” React version of the app we’ve built.

See the Pen 23. Taking it to React Land 🚀 by Jhey.

As stated before, there are different ways to achieve the same goal and we won’t dig into everything. But, we can highlight some of the decisions you may have to make or think about.

For the most part, the functional logic remains the same. But, we can use refs to keep track of certain things. And it’s often the case we need to pass these refs in props to the different components.

return (
  <>
    <AudioVisualization
      start={start}
      recording={recording}
      recorder={recorder}
      timeline={timeline}
      drawRef={draw}
      metadata={metadata}
      src={src}
    />
    <RecorderControls
      onRecord={onRecord}
      recording={recording}
      paused={paused}
      onStop={onStop}
    />
    <RecorderPlayback
      src={src}
      timeline={timeline}
      start={start}
      draw={draw}
      audioRef={audioRef}
      scrub={scrub}
    />
    <Recordings
      recordings={recordings}
      onDownload={onDownload}
      onDelete={onDelete}
      onPlay={onPlay}
    />
  </>
)

For example, consider how we are passing the timeline around in a prop. This is a ref for a GreenSock timeline.

const timeline = React.useRef(gsap.timeline())

And this is because some of the components need access to the visualization timeline. But, we could approach this a different way. The alternative would be to pass in event handling as props and have access to the timeline in the scope. Each way would work. But, each way has trade-offs.

Because we’re working in "React" land, we can shift some of our code to be "Reactive". The clue is in the name, I guess. 😅 For example, instead of trying to pad the timeline and draw things from the parent. We can make the canvas component react to audio src changes. By using React.useEffect, we can re-build the timeline based on the metadata available:

React.useEffect(() => {
  barsRef.current.length = 0
  padTimeline()
  drawRef.current = DRAW
  DRAW()
  if (src === null) {
    metadata.current.length = 0      
  } else if (src && metadata.current.length) {
    metadata.current.forEach(bar => addBar(bar))
    gsap.ticker.add(drawRef.current)
  }
}, [src])

The last part that would be good to mention is how we persist recordings to localStorage with React. For this, we are using a custom hook that we built before in our "Getting Started" guide.

const usePersistentState = (key, initialValue) => {
  const [state, setState] = React.useState(
    window.localStorage.getItem(key)
      ? JSON.parse(window.localStorage.getItem(key))
      : initialValue
  )
  React.useEffect(() => {
    // Stringify so we can read it back
    window.localStorage.setItem(key, JSON.stringify(state))
  }, [key, state])
  return [state, setState]
}

This is neat because we can use it the same as React.useState and we get abstracted away from persisting logic.

// Deleting a recording
setRecordings({
  recordings: [
    ...recordings.filter(recording => recording.id !== idToDelete),
  ],
})
// Saving a recording
const audioSafe = e.target.result
const timestamp = new Date()
const name = prompt('Recording name?')
setRecordings({
  recordings: [
    ...recordings,
    {
      audioBlob: audioSafe,
      metadata: metadata.current,
      name: name || timestamp.toUTCString(),
      id: timestamp.getTime(),
    },
  ],
})

I’d recommend digging into some of the React code and having a play if you’re interested. Some things work a little differently in React land. Could you extend the app and make the visualizer support different visual effects? For example, how about passing colors via props for the fill style?

That’s It!

Wow. You’ve made it to the end! This was a long one.

What started as a case study turned into a guide to visualizing audio with JavaScript. We’ve covered a lot here. But, now you have the fundamentals to go forth and make audio visualizations as I did for Kent.

Last but not least, here’s one that visualizes a waveform using @react-three/fiber:

See the Pen 24. Going to 3D React Land 🚀 by Jhey.

That’s ReactJS, ThreeJS and GreenSock all working together! 💪

There’s so much to go off and explore with this one. I’d love to see where you take the demo app or what you can do with it!

As always, if you have any questions, you know where to find me.

Stay Awesome! ʕ •ᴥ•ʔ

P.S. There is a CodePen Collection containing all the demos seen in the articles along with some bonus ones. 🚀

How to Set Up WordPress Form Tracking in Google Analytics

Are you wondering how your WordPress forms are performing?

Tracking forms in Google Analytics helps you uncover insights about how people interact with your forms, where your leads are coming from, which marketing campaigns are performing the best, and more.

In this article, we’ll show you how to set up WordPress form tracking in Google Analytics.

How to Set up WordPress from tracking in Google Analytics

Why Track WordPress Forms in Google Analytics?

Forms are an essential part of a WordPress website. They help you stay connected with your audience, build an email list, boost eCommerce conversions, and grow your business.

By setting up form tracking in Google Analytics, you get to see how different forms are performing on your website. This way, you can promote high converting forms on your most important page while optimizing low converting forms.

Another benefit of form tracking is that it helps you better understand your audience. You can find out which channel they’re using to find your website and submit a form. Plus, you can also track and reduce form abandonment by tracking them in Google Analytics.

Similarly, it also helps identify how your lead generation campaigns are performing or which referral website is driving the most leads on your site.

That said, let’s look at how you can track WordPress forms in Google Analytics.

Setting Up WordPress Form Tracking in Google Analytics

The best way to set up WordPress form tracking in Google Analytics is by using MonsterInsights. It’s the best Google Analytics plugin for WordPress, and over 3 million professionals use it to uncover insights and use data to grow their business.

Google Analytics doesn’t track WordPress forms by default. You would have to edit code to track your forms. This can be tricky for beginners, as the slightest mistake can mess up your tracking and break your website.

MonsterInsights removes the need for writing code or hiring a developer. It allows you to set up Google Analytics and track WordPress forms without editing code.

MonsterInsights

You can also track website traffic, uncover top referral traffic sources, find out your top-performing posts and pages, and more.

The plugin easily integrates with all of the most popular WordPress form plugins like WPForms, Formidable Forms, Contact Form 7, and more.

For this tutorial, we’ll be using the MonsterInsights Pro plan because it includes the Forms addon, dashboard reports, and other advanced tracking features. There is also a MonsterInsights Lite version that you can use to get started.

First, you’ll need to install and activate the MonsterInsights plugin. Please see our guide on how to install a WordPress plugin for more details.

Upon activation, you’ll be taken to Insights in your WordPress dashboard and see MonsterInsights welcome screen. Go ahead and click the ‘Launch the Wizard’ button to configure the plugin and connect it with Google Analytics.

Launch setup wizard

If you need help, then please follow our guide on how to install Google Analytics in WordPress.

After that, you can head over to the Insights » Addons page from your WordPress admin panel. Next, scroll down to the ‘Forms’ addon and click the ‘Install’ button.

Install forms addon

Once the plugin is installed, you should see the ‘Status’ change from Not Installed to Active.

MonsterInsights will now automatically detect your WordPress form plugin and track your forms in Google Analytics.

To check the settings, you can head over to Insights » Settings from your WordPress dashboard and go to the ‘Conversions’ tab.

MonsterInsights settings - conversions tab

You’ll see that the toggle for Form Conversion Tracking option is already enabled.

Now, are you ready to see how your forms are performing?

See How Your WordPress Forms Are Performing

MonsterInsights makes it super simple to see the data by showing stats inside your WordPress dashboard. This helps save time, as you can quickly find the data you need to make decisions.

To view the report, head over to Insights » Reports from the WordPress dashboard and then click the ‘Forms’ tab.

Forms report

In the report, you’ll see impressions, which is the number of people who have viewed your form. You can also see the conversions, which is the number of people who completed the form, and conversion rates for each form on your website.

Now you can also see WordPress form tracking data in Google Analytics.

First, you’ll need to log in to your Google Analytics account and select your website property from the menu at the top.

Choose a website property

After that, you’ll need to go to Behavior » Events » Top Events from the menu on your left.

You can see different event categories in this section. Go ahead and click on the ‘form’ event category.

Click on form event category

On the next screen, you’ll see the total impressions and conversions for your WordPress form.

You can select the ‘impression’ event action if you’d like to see the number of people who viewed your form or select ‘conversion’ if you want how many visitors submitted your forms.

Select event action for forms

For example, let’s select the ‘impression’ event action.

Next, you’ll see which WordPress forms get the most views on your website.

See event label for form tracking

Besides Google Analytics, there’s another way to see how people interact with your forms. Let’s take a look.

Bonus: Track User Journey in WPForms

If you’re using the WPForms plugin for adding a contact form or any other type of form, then you can see what each user did on your site before submitting a form.

WPForms is the best contact form plugin for WordPress. It offers a User Journey addon that shows the steps your visitors took before submitting a form, such as the pages they viewed or optin campaigns they clicked.

You’ll need the WPForms Pro version because it includes the User Journey addon.

First, you’ll have to install and active the WPForms plugin. For more details, please see our tutorial on how to install a WordPress plugin.

Next, you can head over to WPForms » Addons from your WordPress dashboard. Then navigate to the User Journey Addon and click the ‘Install Addon’ button.

Install user journey addon

Once the addon is installed, you can go to WPForms » Entries from your WordPress dashboard.

After that, select a WordPress form to see the user journey.

Select form entries

For the sake of this tutorial, we’ll view the Simple Contact Form entries.

Next, you can click the ‘View’ button under Actions for any entry and see the user’s steps before submitting the form.

View the action of each user

On the next screen, you’ll see details of your user.

Simply scroll down to the ‘User Journey’ section and see their path before arriving on the contact form and submitting it.

View user journey in WPForms

Using the data, you can better understand your users and see which pages or campaigns they visit before converting into leads.

This way, you can promote your forms on pages that people view the most and increase your marketing campaigns’ visibility to get more leads.

For more details, please see our tutorial on how to track user journey on WordPress lead forms.

We hope our article helped you learn how to set up WordPress form tracking in Google Analytics. You can also see our guide on how to create a free business email address, or check out our expert comparison of the best managed WordPress hosting compared.

If you liked this article, then please subscribe to our YouTube Channel for WordPress video tutorials. You can also find us on Twitter and Facebook.

The post How to Set Up WordPress Form Tracking in Google Analytics first appeared on WPBeginner.

What are the best ways to develop a cost-efficient mobile app?

Hello there. I'd like to develop a mobile app with cutting-edge functionality. A rider app, a passenger app, and an admin portal are all included. It is necessary to register and edit your profile. Can someone provide me with a cost estimate for app development? or else any other way to get a cost-efficient mobile app for my taxi business?. Is anyone is aware of the development field please help me with it.