[{"data":1,"prerenderedAt":5074},["ShallowReactive",2],{"navigation":3,"/blog/case-study/loyalty-program-application-case-study-post":734,"/blog/case-study/loyalty-program-application-case-study-surround":935,"/blog/case-study/loyalty-program-application-case-study-related":940},[4,70,207,312,721],{"title":5,"path":6,"stem":7,"children":8,"page":69},"Case Study","/blog/case-study","blog/case-study",[9,13,17,21,25,29,33,37,41,45,49,53,57,61,65],{"title":10,"path":11,"stem":12},"Ambistream – Multi-Layer Streaming Platform","/blog/case-study/ambistream-building-a-multi-layer-streaming-platform-from-a-spark-of-an-idea","blog/case-study/ambistream-building-a-multi-layer-streaming-platform-from-a-spark-of-an-idea",{"title":14,"path":15,"stem":16},"Custom Chromecast & AirPlay Casting App","/blog/case-study/chromecast-airplay-casting-app-case-study","blog/case-study/chromecast-airplay-casting-app-case-study",{"title":18,"path":19,"stem":20},"Direct Music Licensing Platform Case Study","/blog/case-study/dlm-music-catalog-case-study","blog/case-study/dlm-music-catalog-case-study",{"title":22,"path":23,"stem":24},"Assembly Instructions App - Galeco Mobile App","/blog/case-study/galeco-mobile-app-case-study","blog/case-study/galeco-mobile-app-case-study",{"title":26,"path":27,"stem":28},"MemoSonic: Building an Audio Memory Game","/blog/case-study/how-we-built-memosonic-accessible-audio-memory-game-flutter","blog/case-study/how-we-built-memosonic-accessible-audio-memory-game-flutter",{"title":30,"path":31,"stem":32},"Loyalty Program App for Shopping Mall","/blog/case-study/loyalty-program-application-case-study","blog/case-study/loyalty-program-application-case-study",{"title":34,"path":35,"stem":36},"Mobile Application for Music Catalogs","/blog/case-study/mobile-app-for-music-catalog","blog/case-study/mobile-app-for-music-catalog",{"title":38,"path":39,"stem":40},"Universal Music Data Parser for 20+ Platforms","/blog/case-study/musicdata-lab-universal-music-data-parser-case-study","blog/case-study/musicdata-lab-universal-music-data-parser-case-study",{"title":42,"path":43,"stem":44},"Panther ML/AI Pricing Recommendation Tool","/blog/case-study/panther-pricing-recommendation-tool-case-study","blog/case-study/panther-pricing-recommendation-tool-case-study",{"title":46,"path":47,"stem":48},"4D Grupa Roofing Wholesalers Platform","/blog/case-study/roofing-wholesalers-website-case-study","blog/case-study/roofing-wholesalers-website-case-study",{"title":50,"path":51,"stem":52},"Talent Alpha HR Frontend Platform","/blog/case-study/talent-alpha-hr-platform-case-study","blog/case-study/talent-alpha-hr-platform-case-study",{"title":54,"path":55,"stem":56},"Ticketing & Events Platform Development","/blog/case-study/ticketing-events-platform-case-study","blog/case-study/ticketing-events-platform-case-study",{"title":58,"path":59,"stem":60},"Turn Fans into Superfans — Roadie.co","/blog/case-study/turn-fans-into-superfans-roadie-co","blog/case-study/turn-fans-into-superfans-roadie-co",{"title":62,"path":63,"stem":64},"Walkative 2.0 Global Booking Engine","/blog/case-study/walkative-2-booking-platform-case-study","blog/case-study/walkative-2-booking-platform-case-study",{"title":66,"path":67,"stem":68},"Why Merch Is the New Royalty Check, and How Tech Is Closing the Gap","/blog/case-study/why-merch-is-the-new-royalty-check","blog/case-study/why-merch-is-the-new-royalty-check",false,{"title":71,"path":72,"stem":73,"children":74,"page":69},"Music Data","/blog/music-data","blog/music-data",[75,79,83,87,91,95,99,103,107,111,115,119,123,127,131,135,139,143,147,151,155,159,163,167,171,175,179,183,187,191,195,199,203],{"title":76,"path":77,"stem":78},"13 Distributors, 5 File Formats, Zero Standards -The Reality of Music Royalty Data","/blog/music-data/13-distributors-5-file-formats-zero-standards-the-reality-of-music-royalty-data","blog/music-data/13-distributors-5-file-formats-zero-standards-the-reality-of-music-royalty-data",{"title":80,"path":81,"stem":82},"830 Ways to Say Spotify - Normalizing Music Streaming Data","/blog/music-data/830-ways-to-say-spotify-normalizing-music-streaming-data","blog/music-data/830-ways-to-say-spotify-normalizing-music-streaming-data",{"title":84,"path":85,"stem":86},"Why Music Companies Need AI-Powered Analytics (And How We Built One)","/blog/music-data/ai-powered-analytics-dashboard-django-clickhouse-ollama","blog/music-data/ai-powered-analytics-dashboard-django-clickhouse-ollama",{"title":88,"path":89,"stem":90},"AI Rehearsal: Spaced Repetition for Your Musical Ideas","/blog/music-data/ai-rehearsal-spaced-repetition-for-musical-ideas","blog/music-data/ai-rehearsal-spaced-repetition-for-musical-ideas",{"title":92,"path":93,"stem":94},"Audio Project Organization Is a Mess — Here's Why","/blog/music-data/audio-project-organization-mess","blog/music-data/audio-project-organization-mess",{"title":96,"path":97,"stem":98},"Why Audio Search Is Still Broken and How to Fix It with Embeddings","/blog/music-data/audio-search-broken-fix-with-embeddings","blog/music-data/audio-search-broken-fix-with-embeddings",{"title":100,"path":101,"stem":102},"AI Song Structure Analysis: Intro, Verse, Chorus","/blog/music-data/automatic-song-structure-analysis-how-ai-detects-intro-verse-chorus","blog/music-data/automatic-song-structure-analysis-how-ai-detects-intro-verse-chorus",{"title":104,"path":105,"stem":106},"The Broken Feedback Loop in Music Collaboration","/blog/music-data/broken-feedback-loop-music-collaboration","blog/music-data/broken-feedback-loop-music-collaboration",{"title":108,"path":109,"stem":110},"Building a Claude Skill for DDEX Validation: Automate Music Metadata Checks with AI","/blog/music-data/building-a-claude-skill-for-ddex-validation-music-metadata","blog/music-data/building-a-claude-skill-for-ddex-validation-music-metadata",{"title":112,"path":113,"stem":114},"Building a Custom Music Delivery Platform on the Revelator API","/blog/music-data/building-a-custom-music-delivery-platform-on-the-revelator-api","blog/music-data/building-a-custom-music-delivery-platform-on-the-revelator-api",{"title":116,"path":117,"stem":118},"Building a Suno AI Remix App with Nuxt & Firebase","/blog/music-data/building-a-suno-remix-app-with-nuxt-and-firebase","blog/music-data/building-a-suno-remix-app-with-nuxt-and-firebase",{"title":120,"path":121,"stem":122},"C2PA & DDEX: Authenticity Meets Rights in the Age of AI Music","/blog/music-data/c2pa-and-ddex-authenticity-meets-rights-in-the-age-of-ai-music","blog/music-data/c2pa-and-ddex-authenticity-meets-rights-in-the-age-of-ai-music",{"title":124,"path":125,"stem":126},"C2PA in Music: A Claude MCP for Reading Content Provenance","/blog/music-data/c2pa-in-music-mcp","blog/music-data/c2pa-in-music-mcp",{"title":128,"path":129,"stem":130},"Data Modeling in MongoDB Using Design Patterns","/blog/music-data/data-modeling-in-mongodb-with-the-usage-of-design-patterns","blog/music-data/data-modeling-in-mongodb-with-the-usage-of-design-patterns",{"title":132,"path":133,"stem":134},"Office Hours with MusicTech Lab's DDEX Expert","/blog/music-data/ddex-office-hours-musictech","blog/music-data/ddex-office-hours-musictech",{"title":136,"path":137,"stem":138},"DDEX Open Source Projects Review","/blog/music-data/ddex-open-source-projects-review","blog/music-data/ddex-open-source-projects-review",{"title":140,"path":141,"stem":142},"Extracting Data from Ableton .als and .asd Files","/blog/music-data/extracting-data-from-ableton-als-asd-files","blog/music-data/extracting-data-from-ableton-als-asd-files",{"title":144,"path":145,"stem":146},"Maciej Dulski on Sound Connections Podcast","/blog/music-data/from-startups-to-musictech-maciej-dulski-on-sound-connections-podcast","blog/music-data/from-startups-to-musictech-maciej-dulski-on-sound-connections-podcast",{"title":148,"path":149,"stem":150},"How to Transcribe Video to Text Using OpenAI Whisper","/blog/music-data/how-to-transcribe-video-to-text-using-whisper","blog/music-data/how-to-transcribe-video-to-text-using-whisper",{"title":152,"path":153,"stem":154},"Epidemic Sound MCP with Claude for Devs","/blog/music-data/how-to-use-epidemic-sound-mcp-with-claude","blog/music-data/how-to-use-epidemic-sound-mcp-with-claude",{"title":156,"path":157,"stem":158},"Hybrid Database Model in Django for Speed","/blog/music-data/hybrid-database-model-in-django-as-a-performance-booster","blog/music-data/hybrid-database-model-in-django-as-a-performance-booster",{"title":160,"path":161,"stem":162},"Introduction to generating DDEX file using Python","/blog/music-data/introduction-to-generating-ddex-file-using-python","blog/music-data/introduction-to-generating-ddex-file-using-python",{"title":164,"path":165,"stem":166},"Maintaining Music Tech Tools: The SLA Dilemma for Small Teams","/blog/music-data/maintaining-music-tech-tools-the-sla-dilemma-for-small-teams","blog/music-data/maintaining-music-tech-tools-the-sla-dilemma-for-small-teams",{"title":168,"path":169,"stem":170},"Querying Bandcamp Revenue Reports with Natural Language — Meet mtl-bandcamp-mcp","/blog/music-data/mtl-bandcamp-mcp-open-source-revenue-dashboard","blog/music-data/mtl-bandcamp-mcp-open-source-revenue-dashboard",{"title":172,"path":173,"stem":174},"mtl-metadata-mcp: Open Source Audio Metadata Embedding for Claude Code","/blog/music-data/mtl-metadata-mcp-open-source-audio-metadata-embedding","blog/music-data/mtl-metadata-mcp-open-source-audio-metadata-embedding",{"title":176,"path":177,"stem":178},"MusicTech Resources for Builders","/blog/music-data/musictech-resources-curated-insights-for-the-musictech-builders","blog/music-data/musictech-resources-curated-insights-for-the-musictech-builders",{"title":180,"path":181,"stem":182},"Poland's Creative Tech and MusicTech Rise","/blog/music-data/polands-creative-tech-sector-is-on-the-rise-and-musictech-is-part-of-it","blog/music-data/polands-creative-tech-sector-is-on-the-rise-and-musictech-is-part-of-it",{"title":184,"path":185,"stem":186},"Batch ISRC Enrichment That Turns Messy Catalogs Into Clean Data","/blog/music-data/scout-isrc-metadata-enrichment-spotify-musicbrainz","blog/music-data/scout-isrc-metadata-enrichment-spotify-musicbrainz",{"title":188,"path":189,"stem":190},"Music Self-Publishing: The Emuze.me Story","/blog/music-data/self-publishing-in-the-music-industry-a-tale-of-emuze-me","blog/music-data/self-publishing-in-the-music-industry-a-tale-of-emuze-me",{"title":192,"path":193,"stem":194},"Understanding the API First Approach","/blog/music-data/understanding-the-api-first-approach","blog/music-data/understanding-the-api-first-approach",{"title":196,"path":197,"stem":198},"The Voice Memo Graveyard Problem","/blog/music-data/voice-memo-graveyard-problem","blog/music-data/voice-memo-graveyard-problem",{"title":200,"path":201,"stem":202},"Which Database for Music Data? Redshift vs BigQuery vs ClickHouse and When to Use Each","/blog/music-data/which-database-for-music-data-redshift-vs-bigquery-vs-clickhouse","blog/music-data/which-database-for-music-data-redshift-vs-bigquery-vs-clickhouse",{"title":204,"path":205,"stem":206},"Why we decided to use wavesurfer.js","/blog/music-data/why-we-decided-to-use-wavesurfer","blog/music-data/why-we-decided-to-use-wavesurfer",{"title":208,"path":209,"stem":210,"children":211,"page":69},"Newsletter","/blog/newsletter","blog/newsletter",[212,216,220,224,228,232,236,240,244,248,252,256,260,264,268,272,276,280,284,288,292,296,300,304,308],{"title":213,"path":214,"stem":215},"Music Industry Tech Openings (April 2024 Update)","/blog/newsletter/music-industry-tech-openings-april-2024-update","blog/newsletter/music-industry-tech-openings-april-2024-update",{"title":217,"path":218,"stem":219},"Music Industry Tech Openings (April 2025 Update)","/blog/newsletter/music-industry-tech-openings-april-2025-update","blog/newsletter/music-industry-tech-openings-april-2025-update",{"title":221,"path":222,"stem":223},"Music Industry Tech Openings (August 2024 Update)","/blog/newsletter/music-industry-tech-openings-august-2024-update","blog/newsletter/music-industry-tech-openings-august-2024-update",{"title":225,"path":226,"stem":227},"Music Industry Tech Openings (December 2024 Update)","/blog/newsletter/music-industry-tech-openings-december-2024-update","blog/newsletter/music-industry-tech-openings-december-2024-update",{"title":229,"path":230,"stem":231},"Music Industry Tech Openings (February 2025 Update)","/blog/newsletter/music-industry-tech-openings-february-2025-update","blog/newsletter/music-industry-tech-openings-february-2025-update",{"title":233,"path":234,"stem":235},"Music Industry Tech Openings (January 2025 Update)","/blog/newsletter/music-industry-tech-openings-january-2025-update","blog/newsletter/music-industry-tech-openings-january-2025-update",{"title":237,"path":238,"stem":239},"Music Industry Tech Openings (July 2024 Update)","/blog/newsletter/music-industry-tech-openings-july-2024-update","blog/newsletter/music-industry-tech-openings-july-2024-update",{"title":241,"path":242,"stem":243},"Music Industry Tech Openings (June 2024 Update)","/blog/newsletter/music-industry-tech-openings-june-2024-update","blog/newsletter/music-industry-tech-openings-june-2024-update",{"title":245,"path":246,"stem":247},"Music Industry Tech Openings (March 2025 Update)","/blog/newsletter/music-industry-tech-openings-march-2025-update","blog/newsletter/music-industry-tech-openings-march-2025-update",{"title":249,"path":250,"stem":251},"Music Industry Tech Openings (May 2024 Update)","/blog/newsletter/music-industry-tech-openings-may-2024-update","blog/newsletter/music-industry-tech-openings-may-2024-update",{"title":253,"path":254,"stem":255},"Music Industry Tech Openings (May 2025 Update)","/blog/newsletter/music-industry-tech-openings-may-2025","blog/newsletter/music-industry-tech-openings-may-2025",{"title":257,"path":258,"stem":259},"Music Industry Tech Openings (November 2024 Update)","/blog/newsletter/music-industry-tech-openings-november-2024-update","blog/newsletter/music-industry-tech-openings-november-2024-update",{"title":261,"path":262,"stem":263},"Music Industry Tech Openings (October 2024 Update)","/blog/newsletter/music-industry-tech-openings-october-2024-update","blog/newsletter/music-industry-tech-openings-october-2024-update",{"title":265,"path":266,"stem":267},"Music Industry Tech Openings (September 2024 Update)","/blog/newsletter/music-industry-tech-openings-september-2024-update","blog/newsletter/music-industry-tech-openings-september-2024-update",{"title":269,"path":270,"stem":271},"MusicTech Insights #1 by Maciej Dulski","/blog/newsletter/musictech-insights-1-curated-by-maciej-dulski","blog/newsletter/musictech-insights-1-curated-by-maciej-dulski",{"title":273,"path":274,"stem":275},"Provenance, Not Detection, Is the Durable Answer","/blog/newsletter/musictech-insights-10-curated-by-jim-anderson","blog/newsletter/musictech-insights-10-curated-by-jim-anderson",{"title":277,"path":278,"stem":279},"What CMOs can teach us about innovation in uncertain times","/blog/newsletter/musictech-insights-10-curated-by-joanna-kurkowska","blog/newsletter/musictech-insights-10-curated-by-joanna-kurkowska",{"title":281,"path":282,"stem":283},"Feeling the MusicTech Momentum","/blog/newsletter/musictech-insights-2-curated-by-maciej-dulski","blog/newsletter/musictech-insights-2-curated-by-maciej-dulski",{"title":285,"path":286,"stem":287},"AI in Music: Hype, Hope, and a Human Touch","/blog/newsletter/musictech-insights-3-curated-by-drew-thurlow","blog/newsletter/musictech-insights-3-curated-by-drew-thurlow",{"title":289,"path":290,"stem":291},"The Music Metadata Conundrum","/blog/newsletter/musictech-insights-4-curated-by-amanda-schupf","blog/newsletter/musictech-insights-4-curated-by-amanda-schupf",{"title":293,"path":294,"stem":295},"7 Rounds in the First 10 Days of November 2025","/blog/newsletter/musictech-insights-5-curated-by-maciej-dulski","blog/newsletter/musictech-insights-5-curated-by-maciej-dulski",{"title":297,"path":298,"stem":299},"The End of an Era: It's All About to Crash","/blog/newsletter/musictech-insights-6-curated-by-sigurdur-arnason","blog/newsletter/musictech-insights-6-curated-by-sigurdur-arnason",{"title":301,"path":302,"stem":303},"Low-Code Magic Won't Solve MusicTech Reality","/blog/newsletter/musictech-insights-7-curated-by-mariusz-smenzyk","blog/newsletter/musictech-insights-7-curated-by-mariusz-smenzyk",{"title":305,"path":306,"stem":307},"The New Economics of Game Music","/blog/newsletter/musictech-insights-8-curated-by-kenny-vaughan","blog/newsletter/musictech-insights-8-curated-by-kenny-vaughan",{"title":309,"path":310,"stem":311},"Music Business Meets Direct-to-Fan","/blog/newsletter/musictech-insights-9-curated-by-yaw-asamani","blog/newsletter/musictech-insights-9-curated-by-yaw-asamani",{"title":313,"path":314,"stem":315,"children":316,"page":69},"Software Development","/blog/software-development","blog/software-development",[317,321,325,329,333,337,341,345,349,353,357,361,365,369,373,377,381,385,389,393,397,401,405,409,413,417,421,425,429,433,437,441,445,449,453,457,461,465,469,473,477,481,485,489,493,497,501,505,509,513,517,521,525,529,533,537,541,545,549,553,557,561,565,569,573,577,581,585,589,593,597,601,605,609,613,617,621,625,629,633,637,641,645,649,653,657,661,665,669,673,677,681,685,689,693,697,701,705,709,713,717],{"title":318,"path":319,"stem":320},"Benefits of Outsourcing Software Development","/blog/software-development/10-benefits-of-outsourcing-software-development-services","blog/software-development/10-benefits-of-outsourcing-software-development-services",{"title":322,"path":323,"stem":324},"10 Steps to Find the Best MVP Developers","/blog/software-development/10-steps-to-find-the-best-mvp-developers-for-your-startup-idea","blog/software-development/10-steps-to-find-the-best-mvp-developers-for-your-startup-idea",{"title":326,"path":327,"stem":328},"1,200 Looms Later: How Async Video Became My Development Superpower","/blog/software-development/1200-looms-how-async-video-became-our-development-superpower","blog/software-development/1200-looms-how-async-video-became-our-development-superpower",{"title":330,"path":331,"stem":332},"Communication Strategy in Outsourcing Projects","/blog/software-development/5-steps-to-implement-an-effective-communication-strategy-in-outsourcing-software-development-project","blog/software-development/5-steps-to-implement-an-effective-communication-strategy-in-outsourcing-software-development-project",{"title":334,"path":335,"stem":336},"7 Best Practices for Outsourcing Software Development","/blog/software-development/7-best-practices-for-outsourcing-software-development","blog/software-development/7-best-practices-for-outsourcing-software-development",{"title":338,"path":339,"stem":340},"9 Reasons Why Saleor.io Is Best for eCommerce","/blog/software-development/9-reasons-why-the-saleor-io-platform-is-the-best-choice-for-your-ecommerce-website","blog/software-development/9-reasons-why-the-saleor-io-platform-is-the-best-choice-for-your-ecommerce-website",{"title":342,"path":343,"stem":344},"A Look at Bravelab.io’s Clutch 2021 Year In Review","/blog/software-development/a-look-at-bravelab-ios-clutch-2021-year-in-review","blog/software-development/a-look-at-bravelab-ios-clutch-2021-year-in-review",{"title":346,"path":347,"stem":348},"A quick introduction to profit sharing implementation","/blog/software-development/a-quick-introduction-to-profit-sharing-implementation","blog/software-development/a-quick-introduction-to-profit-sharing-implementation",{"title":350,"path":351,"stem":352},"AI Audio Similarity Search: The Future of Sound Library Discovery","/blog/software-development/ai-audio-similarity-search-for-sound-libraries","blog/software-development/ai-audio-similarity-search-for-sound-libraries",{"title":354,"path":355,"stem":356},"Automate Repetitive Tasks for Better Results","/blog/software-development/automate-repetitive-tasks-to-improve-your-business-performance","blog/software-development/automate-repetitive-tasks-to-improve-your-business-performance",{"title":358,"path":359,"stem":360},"Automating Success: The Art of Unified Documentation","/blog/software-development/automating-success-the-art-of-unified-documentation","blog/software-development/automating-success-the-art-of-unified-documentation",{"title":362,"path":363,"stem":364},"Brave 3.0 Website Redesign, Part 2: Solution","/blog/software-development/brave-3-0-how-we-conducted-website-redesign-part-2-solution","blog/software-development/brave-3-0-how-we-conducted-website-redesign-part-2-solution",{"title":366,"path":367,"stem":368},"Brave 3.0, Part 4: Tech Stack and Recap","/blog/software-development/brave-3-0-part-4-technologies-behind-and-final-series-recap","blog/software-development/brave-3-0-part-4-technologies-behind-and-final-series-recap",{"title":370,"path":371,"stem":372},"Brave 3.0 – redesign process part 1. The Challenge","/blog/software-development/brave-3-0-redesign-process-part-1-challenge","blog/software-development/brave-3-0-redesign-process-part-1-challenge",{"title":374,"path":375,"stem":376},"Brave 3.0 – redesign process, part 3. Lesson learned","/blog/software-development/brave-3-0-redesign-process-part-3-lesson-learned","blog/software-development/brave-3-0-redesign-process-part-3-lesson-learned",{"title":378,"path":379,"stem":380},"Bravelab.io: Top Software Developer by Clutch","/blog/software-development/bravelab-io-is-recognized-as-a-top-custom-software-developer-by-clutch","blog/software-development/bravelab-io-is-recognized-as-a-top-custom-software-developer-by-clutch",{"title":382,"path":383,"stem":384},"Bravelab.io: Top Developer in Poland by Clutch","/blog/software-development/bravelab-io-named-top-software-developer-in-poland-by-clutch","blog/software-development/bravelab-io-named-top-software-developer-in-poland-by-clutch",{"title":386,"path":387,"stem":388},"MusicTech Lab Partners with LALAL.AI","/blog/software-development/bravelab-partners-with-the-audio-lalal-ai","blog/software-development/bravelab-partners-with-the-audio-lalal-ai",{"title":390,"path":391,"stem":392},"MusicTech Lab Partners with The Audio Programmer","/blog/software-development/bravelab-partners-with-the-audio-programmer","blog/software-development/bravelab-partners-with-the-audio-programmer",{"title":394,"path":395,"stem":396},"Bravelab's team about productivity","/blog/software-development/bravelabs-team-about-productivity","blog/software-development/bravelabs-team-about-productivity",{"title":398,"path":399,"stem":400},"Braveloper","/blog/software-development/braveloper","blog/software-development/braveloper",{"title":402,"path":403,"stem":404},"Bravely App: Boost Productivity with Django","/blog/software-development/bravely-app-how-to-be-more-productive-with-django-quick","blog/software-development/bravely-app-how-to-be-more-productive-with-django-quick",{"title":406,"path":407,"stem":408},"DIY MIDI Controller for Ableton with Arduino","/blog/software-development/building-a-diy-midi-controller-for-ableton-live-with-arduino","blog/software-development/building-a-diy-midi-controller-for-ableton-live-with-arduino",{"title":410,"path":411,"stem":412},"C2PA in Ableton: Making AI Music Provenance Visible Inside Your DAW","/blog/software-development/c2pa-in-ableton-max-for-live","blog/software-development/c2pa-in-ableton-max-for-live",{"title":414,"path":415,"stem":416},"Change Detection mechanism in Angular","/blog/software-development/change-detection-mechanism-in-angular","blog/software-development/change-detection-mechanism-in-angular",{"title":418,"path":419,"stem":420},"Communication Channels in Remote Work","/blog/software-development/comparison-of-the-communication-channels-in-remote-work","blog/software-development/comparison-of-the-communication-channels-in-remote-work",{"title":422,"path":423,"stem":424},"Connecting Your Max for Live Device to a Cloud API","/blog/software-development/connecting-your-max-for-live-device-to-a-cloud-api","blog/software-development/connecting-your-max-for-live-device-to-a-cloud-api",{"title":426,"path":427,"stem":428},"From Voice Memo to Studio: The Cross-Platform Problem for Creators","/blog/software-development/cross-platform-problem-for-creators","blog/software-development/cross-platform-problem-for-creators",{"title":430,"path":431,"stem":432},"Cultural transformation through the pandemic era","/blog/software-development/cultural-transformation-through-the-pandemic-era","blog/software-development/cultural-transformation-through-the-pandemic-era",{"title":434,"path":435,"stem":436},"D-Commerce Decoded: Cutting Through the Hype","/blog/software-development/d-commerce-decoded-cutting-through-the-hype","blog/software-development/d-commerce-decoded-cutting-through-the-hype",{"title":438,"path":439,"stem":440},"Dev Meeting 002: Intro to DDD","/blog/software-development/dev-meeting-002-introduction-to-domain-driven-design-ddd","blog/software-development/dev-meeting-002-introduction-to-domain-driven-design-ddd",{"title":442,"path":443,"stem":444},"Dev Meeting 003: Web3 Primer","/blog/software-development/dev-meeting-003-web3-primer","blog/software-development/dev-meeting-003-web3-primer",{"title":446,"path":447,"stem":448},"Dev Meeting 004: Introduction to Event Storming","/blog/software-development/dev-meeting-004-introduction-to-event-storming","blog/software-development/dev-meeting-004-introduction-to-event-storming",{"title":450,"path":451,"stem":452},"Dev Meeting 001: Kubernetes is a Framework","/blog/software-development/dev-meeting-kubernetes-is-a-framework","blog/software-development/dev-meeting-kubernetes-is-a-framework",{"title":454,"path":455,"stem":456},"Did You Know? 10 Developer Tips from Real Codebases","/blog/software-development/did-you-know-dev-tips-part-1","blog/software-development/did-you-know-dev-tips-part-1",{"title":458,"path":459,"stem":460},"10 Surprising MusicTech Facts (Part 2)","/blog/software-development/did-you-know-musictech-facts-part-2","blog/software-development/did-you-know-musictech-facts-part-2",{"title":462,"path":463,"stem":464},"Django-cms and GraphQL","/blog/software-development/django-cms-and-graphql","blog/software-development/django-cms-and-graphql",{"title":466,"path":467,"stem":468},"Does Zappa make it super easy?","/blog/software-development/does-zappa-make-it-super-easy","blog/software-development/does-zappa-make-it-super-easy",{"title":470,"path":471,"stem":472},"Establishing cooperation between Netlify and Bravelab","/blog/software-development/establishing-cooperation-between-netlify-and-bravelab","blog/software-development/establishing-cooperation-between-netlify-and-bravelab",{"title":474,"path":475,"stem":476},"Export Ableton Locators to JSON via Max for Live","/blog/software-development/exporting-ableton-live-locators-to-json-with-max-for-live","blog/software-development/exporting-ableton-live-locators-to-json-with-max-for-live",{"title":478,"path":479,"stem":480},"IT Outsourcing: Success and Failure Factors","/blog/software-development/factors-that-contribute-to-the-success-or-failure-of-an-it-outsourcing-project","blog/software-development/factors-that-contribute-to-the-success-or-failure-of-an-it-outsourcing-project",{"title":482,"path":483,"stem":484},"Flutter 2022 Strategy: Analyzing the Roadmap","/blog/software-development/flutter-strategy-for-2022-analyzing-the-new-flutter-roadmap","blog/software-development/flutter-strategy-for-2022-analyzing-the-new-flutter-roadmap",{"title":486,"path":487,"stem":488},"Git Better #1 — Commit Message Convention","/blog/software-development/git-better-1-see-more-with-a-commit-message-convention","blog/software-development/git-better-1-see-more-with-a-commit-message-convention",{"title":490,"path":491,"stem":492},"Hasura in action. How to use it with Django","/blog/software-development/hasura-in-action","blog/software-development/hasura-in-action",{"title":494,"path":495,"stem":496},"Holacracy why and where we are","/blog/software-development/holacracy-why-and-where-we-are","blog/software-development/holacracy-why-and-where-we-are",{"title":498,"path":499,"stem":500},"How does JavaScript work","/blog/software-development/how-does-javascript-work","blog/software-development/how-does-javascript-work",{"title":502,"path":503,"stem":504},"How important is good UX/UI design?","/blog/software-development/how-important-is-good-ux-ui-design","blog/software-development/how-important-is-good-ux-ui-design",{"title":506,"path":507,"stem":508},"How repetitive tasks impact your business","/blog/software-development/how-repetitive-tasks-impact-your-business","blog/software-development/how-repetitive-tasks-impact-your-business",{"title":510,"path":511,"stem":512},"Becoming a Vue.js Dev: Do Paid Trials Work?","/blog/software-development/how-to-become-a-vue-js-developer-and-whether-paid-trials-in-it-work-out","blog/software-development/how-to-become-a-vue-js-developer-and-whether-paid-trials-in-it-work-out",{"title":514,"path":515,"stem":516},"How to Build an MVP in 6 Steps","/blog/software-development/how-to-build-a-minimum-viable-product-mvp-in-6-steps","blog/software-development/how-to-build-a-minimum-viable-product-mvp-in-6-steps",{"title":518,"path":519,"stem":520},"How to conduct workshops for creative industry?","/blog/software-development/how-to-conduct-workshops-for-creative-industry","blog/software-development/how-to-conduct-workshops-for-creative-industry",{"title":522,"path":523,"stem":524},"How to easily create form in Angular","/blog/software-development/how-to-easily-create-form-in-angular","blog/software-development/how-to-easily-create-form-in-angular",{"title":526,"path":527,"stem":528},"How to export orders in Saleor.io to XLSX file","/blog/software-development/how-to-export-orders-in-saleor-io-to-xlsx-file","blog/software-development/how-to-export-orders-in-saleor-io-to-xlsx-file",{"title":530,"path":531,"stem":532},"Handling High Loads on E-Commerce Platforms","/blog/software-development/how-to-handle-high-loads-on-e-commerce-platform-with-ease","blog/software-development/how-to-handle-high-loads-on-e-commerce-platform-with-ease",{"title":534,"path":535,"stem":536},"How to launch Saleor.io shop instance within 40h","/blog/software-development/how-to-launch-saleor-io-shop-instance-within-40h","blog/software-development/how-to-launch-saleor-io-shop-instance-within-40h",{"title":538,"path":539,"stem":540},"First Steps to Build a Business Relationship","/blog/software-development/how-to-make-the-first-step-to-establish-a-business-relationship","blog/software-development/how-to-make-the-first-step-to-establish-a-business-relationship",{"title":542,"path":543,"stem":544},"Multi-Tenant Apps with Django and Saleor.io","/blog/software-development/how-to-manage-tenants-in-the-multitenant-app-based-on-django-tenants-and-saleor-io-platform","blog/software-development/how-to-manage-tenants-in-the-multitenant-app-based-on-django-tenants-and-saleor-io-platform",{"title":546,"path":547,"stem":548},"Notion Backup Tool Built in 3 Days with Python","/blog/software-development/how-we-built-a-notion-backup-tool-in-3-days-with-pythonvue-and-why","blog/software-development/how-we-built-a-notion-backup-tool-in-3-days-with-pythonvue-and-why",{"title":550,"path":551,"stem":552},"Important new features in Python 3.8","/blog/software-development/important-new-features-in-python-3-8","blog/software-development/important-new-features-in-python-3-8",{"title":554,"path":555,"stem":556},"Installing Proxmox on dedicated server from OVH","/blog/software-development/installing-proxmox-on-dedicated-server-from-ovh","blog/software-development/installing-proxmox-on-dedicated-server-from-ovh",{"title":558,"path":559,"stem":560},"Integrating SignNow E-Signatures into Your Django Application","/blog/software-development/integrating-signnow-e-signatures-into-your-django-application","blog/software-development/integrating-signnow-e-signatures-into-your-django-application",{"title":562,"path":563,"stem":564},"Tempus Metronome and GetSongBPM API","/blog/software-development/integrating-tempus-metronome-with-the-getsongbpm-api-what-bpm-really-means-and-how-to-use-it","blog/software-development/integrating-tempus-metronome-with-the-getsongbpm-api-what-bpm-really-means-and-how-to-use-it",{"title":566,"path":567,"stem":568},"Introducing MusicTech Poland","/blog/software-development/introducing-musictech-poland","blog/software-development/introducing-musictech-poland",{"title":570,"path":571,"stem":572},"Vue.js as a Frontend for Saleor.io","/blog/software-development/is-it-possible-to-use-vue-js-as-a-frontend-for-saleor-io-platform","blog/software-development/is-it-possible-to-use-vue-js-as-a-frontend-for-saleor-io-platform",{"title":574,"path":575,"stem":576},"Is your business ready for the cashless era?","/blog/software-development/is-your-business-ready-for-the-cashless-era","blog/software-development/is-your-business-ready-for-the-cashless-era",{"title":578,"path":579,"stem":580},"Is your face ready to buy?","/blog/software-development/is-your-face-ready-to-buy","blog/software-development/is-your-face-ready-to-buy",{"title":582,"path":583,"stem":584},"JS Frameworks: Trends and Opportunities","/blog/software-development/javascript-trending-frameworks-and-market-opportunities","blog/software-development/javascript-trending-frameworks-and-market-opportunities",{"title":586,"path":587,"stem":588},"Kanban Board: Boost Your Team Productivity","/blog/software-development/kanban-board-methodology-hack-your-companys-productivity","blog/software-development/kanban-board-methodology-hack-your-companys-productivity",{"title":590,"path":591,"stem":592},"Verified Human Cert MCP Server: Prove Your Music Is Human-Made, Right from the Terminal","/blog/software-development/mcp-verified-human-cert-open-source","blog/software-development/mcp-verified-human-cert-open-source",{"title":594,"path":595,"stem":596},"Migrating from TravisCI to Github Actions","/blog/software-development/migrating-from-travisci-to-github-actions","blog/software-development/migrating-from-travisci-to-github-actions",{"title":598,"path":599,"stem":600},"MusicTech Lab: Top Software Developer by Clutch","/blog/software-development/musictechlab-is-recognized-as-a-top-custom-software-developer-by-clutch","blog/software-development/musictechlab-is-recognized-as-a-top-custom-software-developer-by-clutch",{"title":602,"path":603,"stem":604},"MusicTech Lab x Verified Human: Building a Trust Layer for Human-Made Music","/blog/software-development/musictechlab_blog_verified_human_partnership","blog/software-development/musictechlab_blog_verified_human_partnership",{"title":606,"path":607,"stem":608},"MusicXML: Standard for Music Notation","/blog/software-development/musicxml-standard-for-music-notation-and-education","blog/software-development/musicxml-standard-for-music-notation-and-education",{"title":610,"path":611,"stem":612},"Only a few books but dozens of ideas","/blog/software-development/only-a-few-books-but-dozens-of-ideas","blog/software-development/only-a-few-books-but-dozens-of-ideas",{"title":614,"path":615,"stem":616},"Overdue Invoices and Issue Tracker Integration","/blog/software-development/overdue-invoices-integration-with-the-issue-tracking-system","blog/software-development/overdue-invoices-integration-with-the-issue-tracking-system",{"title":618,"path":619,"stem":620},"Performing SAML SSO using JWT in Django","/blog/software-development/performing-saml-sso-using-jwt-in-django","blog/software-development/performing-saml-sso-using-jwt-in-django",{"title":622,"path":623,"stem":624},"Progressive Web Apps for Mobile Development","/blog/software-development/progressive-web-apps-a-new-way-of-creating-mobile-application","blog/software-development/progressive-web-apps-a-new-way-of-creating-mobile-application",{"title":626,"path":627,"stem":628},"Recruitment System: Gmail, Jira, and CRM","/blog/software-development/recruitment-system-integrating-gmail-bravely-jira-slack-and-copper-crm","blog/software-development/recruitment-system-integrating-gmail-bravely-jira-slack-and-copper-crm",{"title":630,"path":631,"stem":632},"Scratch Me: Chrome Extension for Leads","/blog/software-development/scratch-me-a-simple-chrome-extension-which-will-increase-your-productivity","blog/software-development/scratch-me-a-simple-chrome-extension-which-will-increase-your-productivity",{"title":634,"path":635,"stem":636},"Scratch Me – integration with the Copper CRM","/blog/software-development/scratch-me-integration-with-the-copper-crm","blog/software-development/scratch-me-integration-with-the-copper-crm",{"title":638,"path":639,"stem":640},"SignNow MCP Server: E-Signatures Straight from Claude Code","/blog/software-development/signnow-mcp-server-e-signatures-from-claude-code","blog/software-development/signnow-mcp-server-e-signatures-from-claude-code",{"title":642,"path":643,"stem":644},"Music Industry Tech Openings (March 2024 Update)","/blog/software-development/technical-job-opportunities-in-the-music-industry","blog/software-development/technical-job-opportunities-in-the-music-industry",{"title":646,"path":647,"stem":648},"Thanks app – a Management 3.0 solution","/blog/software-development/thanks-app-a-management-3-0-solution","blog/software-development/thanks-app-a-management-3-0-solution",{"title":650,"path":651,"stem":652},"Colonial Pipeline Case: 7 Security Reminders","/blog/software-development/the-case-of-colonial-pipeline-and-7-security-reminders","blog/software-development/the-case-of-colonial-pipeline-and-7-security-reminders",{"title":654,"path":655,"stem":656},"The Evolution and Future of E-commerce Platforms","/blog/software-development/the-evolution-and-future-of-e-commerce-platforms","blog/software-development/the-evolution-and-future-of-e-commerce-platforms",{"title":658,"path":659,"stem":660},"The Gender Gap in the Tech Industry","/blog/software-development/the-gender-gap-in-the-tech-industry","blog/software-development/the-gender-gap-in-the-tech-industry",{"title":662,"path":663,"stem":664},"First Attempt to Implement 4DX at Bravelab.io","/blog/software-development/the-very-first-attempt-to-implement-4dx-in-bravelab-io","blog/software-development/the-very-first-attempt-to-implement-4dx-in-bravelab-io",{"title":666,"path":667,"stem":668},"The WTF Scale: IT Project Complexity","/blog/software-development/the-wtf-programming-scale-measuring-it-project-complexity","blog/software-development/the-wtf-programming-scale-measuring-it-project-complexity",{"title":670,"path":671,"stem":672},"Top 10 articles through the eyes of our developers","/blog/software-development/top-10-articles-through-the-eyes-of-our-developers","blog/software-development/top-10-articles-through-the-eyes-of-our-developers",{"title":674,"path":675,"stem":676},"Top 6 apps made with Flutter","/blog/software-development/top-6-apps-made-with-flutter","blog/software-development/top-6-apps-made-with-flutter",{"title":678,"path":679,"stem":680},"Uber 101: How Uber Made It to the Top","/blog/software-development/uber-101-how-this-ride-sharing-behemoth-made-it-to-the-top","blog/software-development/uber-101-how-this-ride-sharing-behemoth-made-it-to-the-top",{"title":682,"path":683,"stem":684},"MusicTech Lab Partners with Music Glue","/blog/software-development/unifying-artists-and-audiences-exploring-music-glue","blog/software-development/unifying-artists-and-audiences-exploring-music-glue",{"title":686,"path":687,"stem":688},"Why AI Will Defeat Traditional HR","/blog/software-development/warning-why-artificial-intelligence-will-defeat-traditional-hr","blog/software-development/warning-why-artificial-intelligence-will-defeat-traditional-hr",{"title":690,"path":691,"stem":692},"What is a Discovery Document?","/blog/software-development/what-is-discovery-document","blog/software-development/what-is-discovery-document",{"title":694,"path":695,"stem":696},"What is Flutter, and Why is it Worth Considering?","/blog/software-development/what-is-flutter-and-why-is-it-worth-considering","blog/software-development/what-is-flutter-and-why-is-it-worth-considering",{"title":698,"path":699,"stem":700},"What is a Watermarked Song?","/blog/software-development/what-is-watermarked-song","blog/software-development/what-is-watermarked-song",{"title":702,"path":703,"stem":704},"Choosing a Frontend Framework for the Web","/blog/software-development/which-framework-should-you-choose-for-the-frontend-web-platform-development","blog/software-development/which-framework-should-you-choose-for-the-frontend-web-platform-development",{"title":706,"path":707,"stem":708},"Why DAWs Are the Wrong Tool for Starting a Song","/blog/software-development/why-daws-wrong-tool-for-starting-song","blog/software-development/why-daws-wrong-tool-for-starting-song",{"title":710,"path":711,"stem":712},"Why the Programming World Loves Python","/blog/software-development/why-the-programming-world-loves-python","blog/software-development/why-the-programming-world-loves-python",{"title":714,"path":715,"stem":716},"Why We Don't Build Chat From Scratch (And Neither Should You)","/blog/software-development/why-we-dont-build-chat-from-scratch","blog/software-development/why-we-dont-build-chat-from-scratch",{"title":718,"path":719,"stem":720},"Why we use Sanity.io","/blog/software-development/why-we-use-sanity-io","blog/software-development/why-we-use-sanity-io",{"title":722,"path":723,"stem":724,"children":725,"page":69},"Sportstech","/blog/sportstech","blog/sportstech",[726,730],{"title":727,"path":728,"stem":729},"BeatBuddy Replay: Video Analysis App Challenges","/blog/sportstech/beatbuddy-replay-video-analysis-app-for-swimmers-flutter","blog/sportstech/beatbuddy-replay-video-analysis-app-for-swimmers-flutter",{"title":731,"path":732,"stem":733},"How to Create a Watch Face App for Garmin Watch","/blog/sportstech/how-to-create-watch-face-app-for-garmin-watch","blog/sportstech/how-to-create-watch-face-app-for-garmin-watch",{"id":735,"title":30,"authors":736,"badge":737,"body":739,"category":910,"client":911,"date":913,"description":914,"extension":915,"faq":736,"featured":69,"featuredOrder":736,"hidden":916,"image":917,"keyTakeaways":919,"meta":930,"navigation":916,"path":31,"seo":931,"status":736,"stem":32,"tags":932,"teaser":736,"__hash__":934},"posts/blog/case-study/loyalty-program-application-case-study.md",null,{"label":5,"color":738},"#E91E63",{"type":740,"value":741,"toc":898},"minimark",[742,746,749,754,795,799,813,817,820,823,827,830,833,837,839,842,844,848,850,854,877,881],[743,744,745],"p",{},"hidden: true",[743,747,748],{},"The loyalty program application for tablet devices allows issuing gift cards based on the total amount of completed purchases.",[750,751,753],"h2",{"id":752},"technologies-used","Technologies Used",[755,756,757,765,771,777,783,789],"ul",{},[758,759,760,764],"li",{},[761,762,763],"strong",{},"Python"," - Backend development",[758,766,767,770],{},[761,768,769],{},"JavaScript"," - Frontend development",[758,772,773,776],{},[761,774,775],{},"PostgreSQL"," - Database",[758,778,779,782],{},[761,780,781],{},"Django"," - Python web framework",[758,784,785,788],{},[761,786,787],{},"Angular"," - Frontend framework",[758,790,791,794],{},[761,792,793],{},"Barcode Generator"," - Card generation functionality",[750,796,798],{"id":797},"services-deliverables","Services & Deliverables",[755,800,801,804,807,810],{},[758,802,803],{},"Backend Development",[758,805,806],{},"Frontend Development",[758,808,809],{},"Tablet Application",[758,811,812],{},"Custom Software Development",[750,814,816],{"id":815},"challenge","Challenge",[743,818,819],{},"The Forum shopping mall marketing department needed tools to carry out cyclical promotional campaigns related to issuing ATM prepaid cards to shopping mall customers.",[743,821,822],{},"During a short 5-6 week deadline, we had to develop functionalities for registration, verification, and card issuing based on the rules set in the system configuration.",[750,824,826],{"id":825},"solution","Solution",[743,828,829],{},"The application consists of three components: Backend API, Frontend Panel, and Tablet.",[743,831,832],{},"The application has been running continuously in the same configuration since 2017. A well-thought-out database structure allows creating new editions of the loyalty program without the need to involve developers to make changes to the core code.",[750,834,836],{"id":835},"the-work","The Work",[743,838,819],{},[743,840,841],{},"During a short 5-6 week deadline, we had to develop functionalities for registration, verification, and card issuing based on the rules set in the system configuration. The application consists of three components: Backend API, Frontend Panel, and Tablet.",[743,843,832],{},[750,845,847],{"id":846},"outcome","Outcome",[743,849,832],{},[750,851,853],{"id":852},"project-details","Project Details",[755,855,856,862],{},[758,857,858,861],{},[761,859,860],{},"Project Time:"," 4 months",[758,863,864,867],{},[761,865,866],{},"Components:",[755,868,869,872,875],{},[758,870,871],{},"Backend API",[758,873,874],{},"Frontend Panel",[758,876,809],{},[750,878,880],{"id":879},"related-articles","Related Articles",[755,882,883,889,894],{},[758,884,885],{},[886,887,888],"a",{"href":319},"10 Benefits of outsourcing software development services",[758,890,891],{},[886,892,893],{"href":331},"5 steps to implement an effective communication strategy in outsourcing software development projects",[758,895,896],{},[886,897,334],{"href":335},{"title":899,"searchDepth":900,"depth":900,"links":901},"",2,[902,903,904,905,906,907,908,909],{"id":752,"depth":900,"text":753},{"id":797,"depth":900,"text":798},{"id":815,"depth":900,"text":816},{"id":825,"depth":900,"text":826},{"id":835,"depth":900,"text":836},{"id":846,"depth":900,"text":847},{"id":852,"depth":900,"text":853},{"id":879,"depth":900,"text":880},"case-study",{"name":912},"Forum Gliwice","2022-01-01T00:00:00.000Z","How we built a tablet-based loyalty app for Forum shopping mall to issue prepaid gift cards based on purchase amounts, with backend API and admin panel.","md",true,{"src":918},"/images/cdn-migrated/bravelab_experience_forum_gliwice_app.webp",{"enabled":916,"items":920},[921,924,927],{"text":922,"icon":923},"Tablet-based loyalty app running continuously since 2017 with zero core code changes.","i-lucide-tablet",{"text":925,"icon":926},"Built in 5-6 weeks with Backend API, Frontend Panel, and Tablet components.","i-lucide-zap",{"text":928,"icon":929},"Database design allows new loyalty program editions without developer involvement.","i-lucide-database",{},{"title":30,"description":914},[910,933],"ecommerce","BX8fBD_XaDt9QH0yN3vlZYyUvIVCVgiZW6CzI_K4hLw",[936,938],{"title":26,"path":27,"stem":28,"description":937,"children":-1},"The story behind MemoSonic, a Flutter-based educational game that turns sound recognition into play, with accessibility for visually impaired users.",{"title":34,"path":35,"stem":36,"description":939,"children":-1},"A white-label Flutter mobile app for music catalogs — built for publishers, producers, and music lovers. Audio player, playlists, search, downloads, and offline mode.",[941,1260,1413,4707],{"id":942,"title":62,"authors":736,"badge":943,"body":944,"category":910,"client":1235,"date":1238,"description":1239,"extension":915,"faq":736,"featured":69,"featuredOrder":736,"hidden":69,"image":1240,"keyTakeaways":1242,"meta":1254,"navigation":916,"path":1255,"seo":1256,"status":736,"stem":64,"tags":1257,"teaser":736,"__hash__":1258,"score":1259},"posts/blog/case-study/walkative-2-booking-platform-case-study.md",{"label":5,"color":738},{"type":740,"value":945,"toc":1225},[946,949,953,956,960,999,1001,1004,1024,1030,1032,1035,1039,1043,1077,1081,1119,1121,1127,1165,1167],[743,947,948],{},"Walkative 2.0 is a cloud-based booking platform for free walking tours. It automates the entire reservation process, handling thousands of weekly bookings across multiple European cities.",[750,950,952],{"id":951},"about-walkative","About Walkative",[743,954,955],{},"Founded in 2007 in Krakow by a group of passionate guides, Walkative now operates across Europe and beyond, offering free walking tours in dozens of cities. The platform serves as the central hub for managing tours, guides, partners, and reservations.",[750,957,959],{"id":958},"platform-components","Platform Components",[961,962,969,974,979,984,989,994],"div",{"className":963},[964,965,966,967,968],"grid","grid-cols-1","md:grid-cols-3","gap-4","my-8",[970,971],"spotlight-card",{"description":972,"icon":973,"title":871},"RESTful API architecture","i-lucide-server",[970,975],{"description":976,"icon":977,"title":978},"iOS & Android for guides","i-lucide-smartphone","Mobile App",[970,980],{"description":981,"icon":982,"title":983},"Cards, wallets & tipping","i-lucide-credit-card","Stripe Payments",[970,985],{"description":986,"icon":987,"title":988},"TripAdvisor, Viator, GetYourGuide & more","i-lucide-globe","OTA Integrations",[970,990],{"description":991,"icon":992,"title":993},"Content management system","i-lucide-file-text","CMS",[970,995],{"description":996,"icon":997,"title":998},"Embeddable HTML/JS widget","i-lucide-code","Booking Widget",[750,1000,816],{"id":815},[743,1002,1003],{},"Walkative needed a comprehensive booking platform to handle thousands of weekly reservations across multiple cities. The platform had to:",[755,1005,1006,1009,1012,1015,1018,1021],{},[758,1007,1008],{},"Automate the entire reservation process for free walking tours",[758,1010,1011],{},"Support multiple user roles: Tourists, Guides, Partners, Managers, and Administrators",[758,1013,1014],{},"Integrate with major OTA platforms to prevent overbooking",[758,1016,1017],{},"Provide a mobile application for guides to manage their tours on the go",[758,1019,1020],{},"Offer a Direct Booking Widget that could be embedded on partner websites",[758,1022,1023],{},"Handle complex availability management with backup guide functionality",[1025,1026,1027],"note",{},[743,1028,1029],{},"The main challenge was creating a scalable system that could handle growing reservations while maintaining synchronization across multiple booking channels to prevent overbooking.",[750,1031,826],{"id":825},[743,1033,1034],{},"We developed Walkative 2.0 as a comprehensive cloud-based platform with the following key modules:",[1036,1037],"project-timeline",{":items":1038},"[{\"title\":\"Booking Module\",\"description\":\"Simple reservation flow with email confirmations, cancellation links, departure board showing all available tours, and support for up to 7 participants per booking.\",\"icon\":\"i-lucide-ticket\"},{\"title\":\"Availability Module\",\"description\":\"Centralized tour & guide management with time slots, automatic backup guide alerts at 70% capacity, dynamic limits, and calendar view filtered by city.\",\"icon\":\"i-lucide-calendar\"},{\"title\":\"Partners Module\",\"description\":\"Self-service dashboard for tourism companies to manage their own tours, reservations, and guide availability with tailored permissions.\",\"icon\":\"i-lucide-handshake\"},{\"title\":\"Direct Booking Widget\",\"description\":\"Embeddable HTML/JS widget with calendar-based booking, mobile-responsive design, and Google Ads enhanced conversion tracking.\",\"icon\":\"i-lucide-layout-template\"},{\"title\":\"Mobile App — Walkative Guide\",\"description\":\"Dedicated app for guides: view tours, manage participants, track tips, confirm attendance, request backup guides, and access settlements.\",\"icon\":\"i-lucide-smartphone\"},{\"title\":\"OTA Integration Engine\",\"description\":\"Real-time availability sync with TripAdvisor, Viator, GetYourGuide, GuruWalk, Civitatis, and FreeTour to prevent overbooking.\",\"icon\":\"i-lucide-refresh-cw\"},{\"title\":\"Payment Module\",\"description\":\"Stripe integration for credit/debit cards, digital wallets, post-tour payment links, and online tipping for guides.\",\"icon\":\"i-lucide-credit-card\"},{\"title\":\"Notification Module\",\"description\":\"Automated emails with guide photos, 12-hour reminders, SMS alerts for guides, push notifications, and overbooking warnings.\",\"icon\":\"i-lucide-bell\"}]",[750,1040,1042],{"id":1041},"additional-features","Additional Features",[961,1044,1048,1053,1057,1062,1067,1072],{"className":1045},[964,965,1046,1047,967,968],"md:grid-cols-2","lg:grid-cols-3",[970,1049],{"description":1050,"icon":1051,"title":1052},"Tourists, Guides, Partners, Managers, Administrators","i-lucide-users","Multi-Role System",[970,1054],{"description":1055,"icon":992,"title":1056},"Tours, categories, FAQ, and text pages","CMS Module",[970,1058],{"description":1059,"icon":1060,"title":1061},"Post-tour feedback system","i-lucide-star","Ratings & Comments",[970,1063],{"description":1064,"icon":1065,"title":1066},"Promo codes, QR codes, commission tracking","i-lucide-link","Affiliate Module",[970,1068],{"description":1069,"icon":1070,"title":1071},"Facebook, Google, Apple & email/password","i-lucide-log-in","Authentication",[970,1073],{"description":1074,"icon":1075,"title":1076},"Calendar, users, widgets, reports & settings","i-lucide-layout-dashboard","Admin Panel",[750,1078,1080],{"id":1079},"tech-stack","Tech Stack",[961,1082,1087,1090,1093,1097,1102,1105,1109,1114],{"className":1083},[964,1084,966,1085,1086,968],"grid-cols-2","lg:grid-cols-4","gap-3",[970,1088],{"description":1089,"icon":973,"title":871},"RESTful architecture",[970,1091],{"description":1092,"icon":977,"title":978},"iOS & Android",[970,1094],{"description":1095,"icon":982,"title":1096},"Payments & tipping","Stripe",[970,1098],{"description":1099,"icon":1100,"title":1101},"6 platform integrations","i-lucide-plug","OTA APIs",[970,1103],{"description":1104,"icon":992,"title":993},"Content management",[970,1106],{"description":1107,"icon":997,"title":1108},"HTML/JavaScript","Widget",[970,1110],{"description":1111,"icon":1112,"title":1113},"Email, SMS & Push","i-lucide-mail","Notifications",[970,1115],{"description":1116,"icon":1117,"title":1118},"Social & email login","i-lucide-shield","Auth",[750,1120,847],{"id":846},[1122,1123,1124],"tip",{},[743,1125,1126],{},"The platform successfully handles thousands of weekly reservations and continues to scale as Walkative expands to new cities and partners.",[755,1128,1129,1135,1141,1147,1153,1159],{},[758,1130,1131,1134],{},[761,1132,1133],{},"Automated reservations"," — reduced manual work across all booking channels",[758,1136,1137,1140],{},[761,1138,1139],{},"Zero overbooking"," — real-time OTA synchronization keeps availability in sync",[758,1142,1143,1146],{},[761,1144,1145],{},"Partner self-service"," — tourism companies manage tours independently",[758,1148,1149,1152],{},[761,1150,1151],{},"Mobile-first guides"," — dedicated app for on-the-go tour management",[758,1154,1155,1158],{},[761,1156,1157],{},"Flexible embedding"," — Direct Booking Widget works on any website",[758,1160,1161,1164],{},[761,1162,1163],{},"GDPR compliant"," — full data security and privacy compliance",[750,1166,853],{"id":852},[1168,1169,1170,1181],"table",{},[1171,1172,1173],"thead",{},[1174,1175,1176,1179],"tr",{},[1177,1178],"th",{},[1177,1180],{},[1182,1183,1184,1195,1205,1215],"tbody",{},[1174,1185,1186,1192],{},[1187,1188,1189],"td",{},[761,1190,1191],{},"Project Duration",[1187,1193,1194],{},"12+ months",[1174,1196,1197,1202],{},[1187,1198,1199],{},[761,1200,1201],{},"Monthly Visitors",[1187,1203,1204],{},"1M+ (within 12 months)",[1174,1206,1207,1212],{},[1187,1208,1209],{},[761,1210,1211],{},"Booking Channels",[1187,1213,1214],{},"6+ OTA platforms + direct widget",[1174,1216,1217,1222],{},[1187,1218,1219],{},[761,1220,1221],{},"User Roles",[1187,1223,1224],{},"5 (Tourist, Guide, Partner, Manager, Admin)",{"title":899,"searchDepth":900,"depth":900,"links":1226},[1227,1228,1229,1230,1231,1232,1233,1234],{"id":951,"depth":900,"text":952},{"id":958,"depth":900,"text":959},{"id":815,"depth":900,"text":816},{"id":825,"depth":900,"text":826},{"id":1041,"depth":900,"text":1042},{"id":1079,"depth":900,"text":1080},{"id":846,"depth":900,"text":847},{"id":852,"depth":900,"text":853},{"name":1236,"logo":1237},"Walkative","/images/logos/walkative.webp","2025-06-01T00:00:00.000Z","How we built a cloud-based booking platform for the leading free walking tour company, handling thousands of weekly reservations across European cities.",{"src":1241,"hasLogo":916},"/images/case-studies/Walkative_MusicTech_Lab_Case_Study.webp",{"enabled":916,"items":1243},[1244,1246,1249,1252],{"text":1245,"icon":987},"Cloud booking platform handling thousands of weekly reservations across European cities.",{"text":1247,"icon":1248},"Real-time sync with 6+ OTA platforms like Viator and GetYourGuide prevents overbooking.","i-lucide-refresh-cw",{"text":1250,"icon":1251},"1M+ monthly visitors within 12 months of launch.","i-lucide-trending-up",{"text":1253,"icon":977},"Dedicated Flutter mobile app for guides with tips, settlements, and tour management.",{},"/blog/case-studies/walkative-2-booking-platform-case-study",{"title":62,"description":1239},[910,933],"x_ODpClQFHdokCOwWM6emLgxc0pEUWkaqevL2oJx-YU",4,{"id":1261,"title":54,"authors":736,"badge":1262,"body":1263,"category":910,"client":1394,"date":913,"description":1396,"extension":915,"faq":736,"featured":69,"featuredOrder":736,"hidden":69,"image":1397,"keyTakeaways":1399,"meta":1408,"navigation":916,"path":1409,"seo":1410,"status":736,"stem":56,"tags":1411,"teaser":736,"__hash__":1412,"score":1259},"posts/blog/case-study/ticketing-events-platform-case-study.md",{"label":5,"color":738},{"type":740,"value":1264,"toc":1385},[1265,1268,1271,1273,1288,1292,1295,1298,1303,1307,1313,1319,1323,1328,1334,1342,1344,1347,1349,1369,1371],[743,1266,1267],{},"Solidstudio needed developers with a specific skill set. Their client was building a ticketing platform and needed to integrate the core product with multiple third-party providers through dedicated APIs.",[743,1269,1270],{},"The internal team lacked engineers who could handle this work — but the integrations were a key revenue driver.",[750,1272,753],{"id":752},[961,1274,1276,1280,1284],{"className":1275},[964,965,966,967,968],[970,1277],{"description":1278,"title":1279},"Backend development","Python 3",[970,1281],{"description":1282,"title":1283},"Asynchronous programming","asyncio",[970,1285],{"description":1286,"title":1287},"Modern Python web framework","FastAPI",[750,1289,1291],{"id":1290},"how-we-worked","How We Worked",[743,1293,1294],{},"Our engineers joined the client's in-house team. They participated in standups, used the company chat, and shared availability — but their focus stayed on one thing: building integrations with external partners.",[743,1296,1297],{},"When our team joined, there were zero external integrations. Within months, they delivered over a dozen — each one directly translating into new revenue for the client.",[1122,1299,1300],{},[743,1301,1302],{},"The client was positively surprised by the velocity. Working solutions were delivered within weeks of starting.",[750,1304,1306],{"id":1305},"team-testimonial","Team Testimonial",[1308,1309,1310],"blockquote",{},[743,1311,1312],{},"Since joining the project, I have had an excellent opportunity to grow. I was engaged for more than one year — the best learning period of my career. I was changing alongside the project, and it was a joy to participate in it.",[743,1314,1315,1318],{},[761,1316,1317],{},"Szymon Zmilczak"," — Python Senior Developer",[750,1320,1322],{"id":1321},"client-testimonial","Client Testimonial",[1308,1324,1325],{},[743,1326,1327],{},"I trust their developers with all the technical decisions they make because they're very senior. I'm very happy with their ability to adjust to our needs. They are always responsive, collaborative, and adhere well to any budgetary or timescale requirements.",[743,1329,1330,1333],{},[761,1331,1332],{},"Paweł Małkowiak"," — Co-Founder, Solidstudio",[743,1335,1336],{},[886,1337,1341],{"href":1338,"rel":1339},"https://clutch.co/profile/musictech-lab#review-1066514",[1340],"nofollow","See full review on Clutch",[750,1343,847],{"id":846},[743,1345,1346],{},"The first project was handled entirely by our team. It's live, being used by the end client, and user feedback has been very positive. For the follow-up project, our developer met the initial requirements and became a trusted part of the team.",[750,1348,853],{"id":852},[755,1350,1351,1357,1363],{},[758,1352,1353,1356],{},[761,1354,1355],{},"Duration:"," 24+ months",[758,1358,1359,1362],{},[761,1360,1361],{},"Services:"," Backend Development, API Integration, IT Staff Augmentation",[758,1364,1365,1368],{},[761,1366,1367],{},"Team:"," Oleksandr, Paweł, Jakub, Szymon — Python Developers",[750,1370,880],{"id":879},[755,1372,1373,1377,1381],{},[758,1374,1375],{},[886,1376,888],{"href":319},[758,1378,1379],{},[886,1380,893],{"href":331},[758,1382,1383],{},[886,1384,334],{"href":335},{"title":899,"searchDepth":900,"depth":900,"links":1386},[1387,1388,1389,1390,1391,1392,1393],{"id":752,"depth":900,"text":753},{"id":1290,"depth":900,"text":1291},{"id":1305,"depth":900,"text":1306},{"id":1321,"depth":900,"text":1322},{"id":846,"depth":900,"text":847},{"id":852,"depth":900,"text":853},{"id":879,"depth":900,"text":880},{"name":1395},"Solidstudio","How we provided a Python development team to build third-party API integrations for a ticketing platform, through long-term team augmentation.",{"src":1398},"/images/cdn-migrated/bravelab_experience_solidstudio.webp",{"enabled":916,"items":1400},[1401,1403,1406],{"text":1402,"icon":997},"Over a dozen third-party API integrations delivered from zero within months.",{"text":1404,"icon":1405},"24+ month team augmentation engagement with 4 Python developers.","i-lucide-handshake",{"text":1407,"icon":1251},"Each integration directly translated into new revenue for the client.",{},"/blog/case-studies/ticketing-events-platform-case-study",{"title":54,"description":1396},[910,933],"7q7_6IaklPlqwPe87srrU9NNva15SSSc6KAuLdMN7aA",{"id":1414,"title":26,"authors":1415,"badge":736,"body":1421,"category":910,"client":4682,"date":4685,"description":937,"extension":915,"faq":736,"featured":69,"featuredOrder":736,"hidden":69,"image":4686,"keyTakeaways":4688,"meta":4701,"navigation":916,"path":4702,"seo":4703,"status":736,"stem":28,"tags":4704,"teaser":736,"__hash__":4706,"score":1713},"posts/blog/case-study/how-we-built-memosonic-accessible-audio-memory-game-flutter.md",[1416],{"name":1417,"to":1418,"avatar":1419},"Mariusz Smenżyk","https://www.linkedin.com/in/msmenzyk",{"src":1420},"/images/people/mariusz-smenzyk2.webp",{"type":740,"value":1422,"toc":4621},[1423,1427,1430,1437,1440,1462,1465,1469,1472,1492,1495,1500,1546,1548,1552,1556,1559,1570,1576,1580,1587,1590,1615,1618,1628,1630,1634,1637,1644,1649,1655,1659,1662,1682,1947,1957,1960,1966,1970,1976,2226,2232,2238,2244,2247,2553,2556,2560,2566,2805,2808,2812,2815,2821,2932,2938,2966,2972,3039,3043,3046,3127,3133,3137,3140,3166,3172,3174,3178,3181,3185,3201,3206,3211,3216,3221,3225,3230,3235,3237,3241,3244,3248,3251,3257,3271,3275,3278,3284,3286,3290,3293,3352,3366,3369,3372,3374,3378,3381,3385,3388,3393,3396,3410,3424,3428,3431,3436,3450,3455,3466,3471,3482,3487,3498,3500,3504,3508,3514,3518,3610,3614,3617,3846,3853,3855,3859,3863,3866,3870,3873,3884,3888,3891,3895,3898,3912,3915,3917,3921,3927,3930,3935,3946,3951,3962,3967,3978,3983,3986,3991,3999,4010,4013,4015,4019,4022,4027,4041,4045,4048,4062,4065,4071,4074,4078,4084,4094,4100,4106,4110,4113,4151,4154,4158,4161,4164,4167,4193,4197,4200,4205,4208,4214,4220,4225,4227,4231,4234,4267,4270,4272,4276,4279,4305,4308,4310,4314,4344,4346,4350,4353,4357,4366,4369,4426,4430,4436,4450,4456,4462,4466,4469,4472,4510,4513,4527,4531,4538,4541,4545,4548,4574,4581,4584,4586,4590,4617],[750,1424,1426],{"id":1425},"it-started-with-a-simple-game","It Started with a Simple Game",[743,1428,1429],{},"Picture this: a rainy Sunday afternoon, kids bouncing off the walls, and a parent desperately trying to find something educational yet fun. We pulled out the classic memory card game - flip two cards, find matching pairs.",[743,1431,1432,1433],{},"The kids loved it. But something clicked in my head: ",[1434,1435,1436],"em",{},"What if instead of matching pictures, we matched sounds?",[743,1438,1439],{},"That question sparked MemoSonic.",[961,1441,1447,1448,1447,1452,1447,1459],{"className":1442},[1443,1444,1445,1446],"flex","flex-row","items-center","justify-center","\n  ",[961,1449],{"className":1450},[1451],"w-1/4",[1453,1454],"img",{"src":1455,"alt":1456,"className":1457},"/images/blog/musictechlab_blog_how-we-built-memosonic_inline_0.webp","MemoSonic home screen",[1458],"w-1/2",[961,1460],{"className":1461},[1451],[1463,1464],"hr",{},[750,1466,1468],{"id":1467},"the-problem-visual-learning-isnt-everything","The Problem: Visual Learning Isn't Everything",[743,1470,1471],{},"Traditional memory games are purely visual. You see an image, remember its position, find its pair. Great for training visual memory, but what about:",[755,1473,1474,1480,1486],{},[758,1475,1476,1479],{},[761,1477,1478],{},"Auditory learners"," who process information better through sound?",[758,1481,1482,1485],{},[761,1483,1484],{},"Young musicians"," trying to recognize chords, scales, or instrument timbres?",[758,1487,1488,1491],{},[761,1489,1490],{},"Visually impaired children"," who can't participate in traditional memory games at all?",[743,1493,1494],{},"We realized there was a gap. A big one.",[1496,1497,1499],"h3",{"id":1498},"what-we-wanted-to-build","What We Wanted to Build:",[1168,1501,1502,1512],{},[1171,1503,1504],{},[1174,1505,1506,1509],{},[1177,1507,1508],{},"Traditional Memory",[1177,1510,1511],{},"MemoSonic",[1182,1513,1514,1522,1530,1538],{},[1174,1515,1516,1519],{},[1187,1517,1518],{},"Visual only",[1187,1520,1521],{},"Sound-first approach",[1174,1523,1524,1527],{},[1187,1525,1526],{},"Static images",[1187,1528,1529],{},"Interactive audio feedback",[1174,1531,1532,1535],{},[1187,1533,1534],{},"Limited accessibility",[1187,1536,1537],{},"Inclusive by design",[1174,1539,1540,1543],{},[1187,1541,1542],{},"One learning style",[1187,1544,1545],{},"Multiple categories for different interests",[1463,1547],{},[750,1549,1551],{"id":1550},"building-memosonic-from-idea-to-app","Building MemoSonic: From Idea to App",[1496,1553,1555],{"id":1554},"choosing-the-tech-stack","Choosing the Tech Stack",[743,1557,1558],{},"We needed something that would:",[755,1560,1561,1564,1567],{},[758,1562,1563],{},"Work on both iOS and Android",[758,1565,1566],{},"Handle audio playback smoothly",[758,1568,1569],{},"Feel responsive and polished",[743,1571,1572,1575],{},[761,1573,1574],{},"Flutter"," was the obvious choice. Cross-platform, beautiful animations out of the box, and a fantastic ecosystem for audio libraries.",[1496,1577,1579],{"id":1578},"the-audio-challenge","The Audio Challenge",[743,1581,1582,1583,1586],{},"Here's something most developers don't think about: ",[761,1584,1585],{},"audio is hard",".",[743,1588,1589],{},"Not the playback itself - that's straightforward. The hard part is:",[1591,1592,1593,1603,1609],"ol",{},[758,1594,1595,1598,1599,1602],{},[761,1596,1597],{},"Timing"," - When a player taps a card, the sound needs to play ",[1434,1600,1601],{},"instantly",". Even 100ms delay feels wrong.",[758,1604,1605,1608],{},[761,1606,1607],{},"Overlapping sounds"," - What happens when someone taps two cards quickly? Do sounds cut off? Layer?",[758,1610,1611,1614],{},[761,1612,1613],{},"Memory management"," - 100+ audio files across categories. Load them all? Stream them? Preload the current level?",[743,1616,1617],{},"We went through several iterations:",[1619,1620,1625],"pre",{"className":1621,"code":1623,"language":1624},[1622],"language-text","Version 1: Load all audio upfront\n→ Problem: 3-second app startup, memory issues\n\nVersion 2: Stream audio on demand\n→ Problem: Noticeable delay on first play\n\nVersion 3 (Final): Preload current category, lazy-load others\n→ Sweet spot of performance and responsiveness\n","text",[1626,1627,1623],"code",{"__ignoreMap":899},[1463,1629],{},[750,1631,1633],{"id":1632},"the-content-pipeline-generating-300-audio-assets","The Content Pipeline: Generating 300+ Audio Assets",[743,1635,1636],{},"Here's where it gets nerdy (in the best way).",[743,1638,1639,1640,1643],{},"When we started MemoSonic, we had a problem: we needed ",[761,1641,1642],{},"hundreds of audio files",". 168 chord sounds. 14 scale recordings. 8 rhythm patterns. Individual notes. Where do you even get that?",[743,1645,1646],{},[761,1647,1648],{},"Answer: You generate them programmatically.",[743,1650,1651],{},[1453,1652],{"alt":1653,"src":1654},"Terminal output","/images/blog/musictechlab_blog_how-we-built-memosonic_inline_2.png",[1496,1656,1658],{"id":1657},"synthesizing-piano-chords-with-fluidsynth","Synthesizing Piano Chords with FluidSynth",[743,1660,1661],{},"Instead of recording a pianist playing every chord (expensive, time-consuming), we wrote Python scripts that:",[1591,1663,1664,1670,1676],{},[758,1665,1666,1669],{},[761,1667,1668],{},"Generate MIDI files"," with the exact notes for each chord",[758,1671,1672,1675],{},[761,1673,1674],{},"Render them through FluidSynth"," using a high-quality Salamander Grand Piano soundfont",[758,1677,1678,1681],{},[761,1679,1680],{},"Convert to MP3"," via FFmpeg",[1619,1683,1687],{"className":1684,"code":1685,"language":1686,"meta":899,"style":899},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# Chord intervals (semitones from root)\nCHORD_TYPES = {\n    \"maj\": [0, 4, 7],           # Major: root, major 3rd, perfect 5th\n    \"min\": [0, 3, 7],           # Minor: root, minor 3rd, perfect 5th\n    \"7\": [0, 4, 7, 10],         # Dominant 7th\n    \"m7\": [0, 3, 7, 10],        # Minor 7th\n    \"maj7\": [0, 4, 7, 11],      # Major 7th\n    \"aug\": [0, 4, 8],           # Augmented\n    \"dim\": [0, 3, 6],           # Diminished\n}\n","python",[1626,1688,1689,1698,1711,1751,1780,1814,1847,1881,1911,1941],{"__ignoreMap":899},[1690,1691,1694],"span",{"class":1692,"line":1693},"line",1,[1690,1695,1697],{"class":1696},"sHwdD","# Chord intervals (semitones from root)\n",[1690,1699,1700,1704,1708],{"class":1692,"line":900},[1690,1701,1703],{"class":1702},"sTEyZ","CHORD_TYPES ",[1690,1705,1707],{"class":1706},"sMK4o","=",[1690,1709,1710],{"class":1706}," {\n",[1690,1712,1714,1717,1721,1724,1727,1730,1734,1737,1740,1742,1745,1748],{"class":1692,"line":1713},3,[1690,1715,1716],{"class":1706},"    \"",[1690,1718,1720],{"class":1719},"sfazB","maj",[1690,1722,1723],{"class":1706},"\"",[1690,1725,1726],{"class":1706},":",[1690,1728,1729],{"class":1706}," [",[1690,1731,1733],{"class":1732},"sbssI","0",[1690,1735,1736],{"class":1706},",",[1690,1738,1739],{"class":1732}," 4",[1690,1741,1736],{"class":1706},[1690,1743,1744],{"class":1732}," 7",[1690,1746,1747],{"class":1706},"],",[1690,1749,1750],{"class":1696},"           # Major: root, major 3rd, perfect 5th\n",[1690,1752,1753,1755,1758,1760,1762,1764,1766,1768,1771,1773,1775,1777],{"class":1692,"line":1259},[1690,1754,1716],{"class":1706},[1690,1756,1757],{"class":1719},"min",[1690,1759,1723],{"class":1706},[1690,1761,1726],{"class":1706},[1690,1763,1729],{"class":1706},[1690,1765,1733],{"class":1732},[1690,1767,1736],{"class":1706},[1690,1769,1770],{"class":1732}," 3",[1690,1772,1736],{"class":1706},[1690,1774,1744],{"class":1732},[1690,1776,1747],{"class":1706},[1690,1778,1779],{"class":1696},"           # Minor: root, minor 3rd, perfect 5th\n",[1690,1781,1783,1785,1788,1790,1792,1794,1796,1798,1800,1802,1804,1806,1809,1811],{"class":1692,"line":1782},5,[1690,1784,1716],{"class":1706},[1690,1786,1787],{"class":1719},"7",[1690,1789,1723],{"class":1706},[1690,1791,1726],{"class":1706},[1690,1793,1729],{"class":1706},[1690,1795,1733],{"class":1732},[1690,1797,1736],{"class":1706},[1690,1799,1739],{"class":1732},[1690,1801,1736],{"class":1706},[1690,1803,1744],{"class":1732},[1690,1805,1736],{"class":1706},[1690,1807,1808],{"class":1732}," 10",[1690,1810,1747],{"class":1706},[1690,1812,1813],{"class":1696},"         # Dominant 7th\n",[1690,1815,1817,1819,1822,1824,1826,1828,1830,1832,1834,1836,1838,1840,1842,1844],{"class":1692,"line":1816},6,[1690,1818,1716],{"class":1706},[1690,1820,1821],{"class":1719},"m7",[1690,1823,1723],{"class":1706},[1690,1825,1726],{"class":1706},[1690,1827,1729],{"class":1706},[1690,1829,1733],{"class":1732},[1690,1831,1736],{"class":1706},[1690,1833,1770],{"class":1732},[1690,1835,1736],{"class":1706},[1690,1837,1744],{"class":1732},[1690,1839,1736],{"class":1706},[1690,1841,1808],{"class":1732},[1690,1843,1747],{"class":1706},[1690,1845,1846],{"class":1696},"        # Minor 7th\n",[1690,1848,1850,1852,1855,1857,1859,1861,1863,1865,1867,1869,1871,1873,1876,1878],{"class":1692,"line":1849},7,[1690,1851,1716],{"class":1706},[1690,1853,1854],{"class":1719},"maj7",[1690,1856,1723],{"class":1706},[1690,1858,1726],{"class":1706},[1690,1860,1729],{"class":1706},[1690,1862,1733],{"class":1732},[1690,1864,1736],{"class":1706},[1690,1866,1739],{"class":1732},[1690,1868,1736],{"class":1706},[1690,1870,1744],{"class":1732},[1690,1872,1736],{"class":1706},[1690,1874,1875],{"class":1732}," 11",[1690,1877,1747],{"class":1706},[1690,1879,1880],{"class":1696},"      # Major 7th\n",[1690,1882,1884,1886,1889,1891,1893,1895,1897,1899,1901,1903,1906,1908],{"class":1692,"line":1883},8,[1690,1885,1716],{"class":1706},[1690,1887,1888],{"class":1719},"aug",[1690,1890,1723],{"class":1706},[1690,1892,1726],{"class":1706},[1690,1894,1729],{"class":1706},[1690,1896,1733],{"class":1732},[1690,1898,1736],{"class":1706},[1690,1900,1739],{"class":1732},[1690,1902,1736],{"class":1706},[1690,1904,1905],{"class":1732}," 8",[1690,1907,1747],{"class":1706},[1690,1909,1910],{"class":1696},"           # Augmented\n",[1690,1912,1914,1916,1919,1921,1923,1925,1927,1929,1931,1933,1936,1938],{"class":1692,"line":1913},9,[1690,1915,1716],{"class":1706},[1690,1917,1918],{"class":1719},"dim",[1690,1920,1723],{"class":1706},[1690,1922,1726],{"class":1706},[1690,1924,1729],{"class":1706},[1690,1926,1733],{"class":1732},[1690,1928,1736],{"class":1706},[1690,1930,1770],{"class":1732},[1690,1932,1736],{"class":1706},[1690,1934,1935],{"class":1732}," 6",[1690,1937,1747],{"class":1706},[1690,1939,1940],{"class":1696},"           # Diminished\n",[1690,1942,1944],{"class":1692,"line":1943},10,[1690,1945,1946],{"class":1706},"}\n",[743,1948,1949,1950,1953,1954,1586],{},"12 root notes × 7 chord types = ",[761,1951,1952],{},"84 chords",". Add enharmonic equivalents (C# = Db, etc.) and we hit ",[761,1955,1956],{},"168 unique chord files",[743,1958,1959],{},"The pipeline:",[1619,1961,1964],{"className":1962,"code":1963,"language":1624},[1622],"MIDI generation (midiutil)\n    ↓\nFluidSynth + Salamander Piano SF2\n    ↓\nWAV file\n    ↓\nFFmpeg MP3 encoding\n    ↓\nFinal audio asset\n",[1626,1965,1963],{"__ignoreMap":899},[1496,1967,1969],{"id":1968},"synthesizing-drum-patterns-from-scratch","Synthesizing Drum Patterns from Scratch",[743,1971,1972,1973,1586],{},"The rhythm category was even more interesting. We didn't use samples at all - we ",[761,1974,1975],{},"synthesized every drum sound mathematically",[1619,1977,1979],{"className":1684,"code":1978,"language":1686,"meta":899,"style":899},"def generate_kick(duration_ms=150):\n    \"\"\"Generate a punchy kick drum sound\"\"\"\n    for i in range(num_samples):\n        t = i / SAMPLE_RATE\n        # Pitch envelope: starts high, drops quickly\n        freq = freq_end + (freq_start - freq_end) * math.exp(-decay_rate * t)\n\n        # Main tone with pitch drop\n        tone = math.sin(2 * math.pi * freq * t)\n\n        # Add click transient at the start\n        if t \u003C 0.005:\n            click = (1 - t / 0.005) * 0.5\n        ...\n",[1626,1980,1981,2006,2018,2039,2054,2059,2113,2118,2123,2163,2167,2173,2191,2220],{"__ignoreMap":899},[1690,1982,1983,1987,1991,1994,1998,2000,2003],{"class":1692,"line":1693},[1690,1984,1986],{"class":1985},"spNyl","def",[1690,1988,1990],{"class":1989},"s2Zo4"," generate_kick",[1690,1992,1993],{"class":1706},"(",[1690,1995,1997],{"class":1996},"sHdIc","duration_ms",[1690,1999,1707],{"class":1706},[1690,2001,2002],{"class":1732},"150",[1690,2004,2005],{"class":1706},"):\n",[1690,2007,2008,2012,2015],{"class":1692,"line":900},[1690,2009,2011],{"class":2010},"s7zQu","    \"\"\"",[1690,2013,2014],{"class":1696},"Generate a punchy kick drum sound",[1690,2016,2017],{"class":2010},"\"\"\"\n",[1690,2019,2020,2023,2026,2029,2032,2034,2037],{"class":1692,"line":1713},[1690,2021,2022],{"class":2010},"    for",[1690,2024,2025],{"class":1702}," i ",[1690,2027,2028],{"class":2010},"in",[1690,2030,2031],{"class":1989}," range",[1690,2033,1993],{"class":1706},[1690,2035,2036],{"class":1989},"num_samples",[1690,2038,2005],{"class":1706},[1690,2040,2041,2044,2046,2048,2051],{"class":1692,"line":1259},[1690,2042,2043],{"class":1702},"        t ",[1690,2045,1707],{"class":1706},[1690,2047,2025],{"class":1702},[1690,2049,2050],{"class":1706},"/",[1690,2052,2053],{"class":1702}," SAMPLE_RATE\n",[1690,2055,2056],{"class":1692,"line":1782},[1690,2057,2058],{"class":1696},"        # Pitch envelope: starts high, drops quickly\n",[1690,2060,2061,2064,2066,2069,2072,2075,2078,2081,2084,2087,2090,2093,2095,2098,2101,2104,2107,2110],{"class":1692,"line":1816},[1690,2062,2063],{"class":1702},"        freq ",[1690,2065,1707],{"class":1706},[1690,2067,2068],{"class":1702}," freq_end ",[1690,2070,2071],{"class":1706},"+",[1690,2073,2074],{"class":1706}," (",[1690,2076,2077],{"class":1702},"freq_start ",[1690,2079,2080],{"class":1706},"-",[1690,2082,2083],{"class":1702}," freq_end",[1690,2085,2086],{"class":1706},")",[1690,2088,2089],{"class":1706}," *",[1690,2091,2092],{"class":1702}," math",[1690,2094,1586],{"class":1706},[1690,2096,2097],{"class":1989},"exp",[1690,2099,2100],{"class":1706},"(-",[1690,2102,2103],{"class":1989},"decay_rate ",[1690,2105,2106],{"class":1706},"*",[1690,2108,2109],{"class":1989}," t",[1690,2111,2112],{"class":1706},")\n",[1690,2114,2115],{"class":1692,"line":1849},[1690,2116,2117],{"emptyLinePlaceholder":916},"\n",[1690,2119,2120],{"class":1692,"line":1883},[1690,2121,2122],{"class":1696},"        # Main tone with pitch drop\n",[1690,2124,2125,2128,2130,2132,2134,2137,2139,2142,2144,2146,2148,2152,2154,2157,2159,2161],{"class":1692,"line":1913},[1690,2126,2127],{"class":1702},"        tone ",[1690,2129,1707],{"class":1706},[1690,2131,2092],{"class":1702},[1690,2133,1586],{"class":1706},[1690,2135,2136],{"class":1989},"sin",[1690,2138,1993],{"class":1706},[1690,2140,2141],{"class":1732},"2",[1690,2143,2089],{"class":1706},[1690,2145,2092],{"class":1989},[1690,2147,1586],{"class":1706},[1690,2149,2151],{"class":2150},"swJcz","pi",[1690,2153,2089],{"class":1706},[1690,2155,2156],{"class":1989}," freq ",[1690,2158,2106],{"class":1706},[1690,2160,2109],{"class":1989},[1690,2162,2112],{"class":1706},[1690,2164,2165],{"class":1692,"line":1943},[1690,2166,2117],{"emptyLinePlaceholder":916},[1690,2168,2170],{"class":1692,"line":2169},11,[1690,2171,2172],{"class":1696},"        # Add click transient at the start\n",[1690,2174,2176,2179,2182,2185,2188],{"class":1692,"line":2175},12,[1690,2177,2178],{"class":2010},"        if",[1690,2180,2181],{"class":1702}," t ",[1690,2183,2184],{"class":1706},"\u003C",[1690,2186,2187],{"class":1732}," 0.005",[1690,2189,2190],{"class":1706},":\n",[1690,2192,2194,2197,2199,2201,2204,2207,2209,2211,2213,2215,2217],{"class":1692,"line":2193},13,[1690,2195,2196],{"class":1702},"            click ",[1690,2198,1707],{"class":1706},[1690,2200,2074],{"class":1706},[1690,2202,2203],{"class":1732},"1",[1690,2205,2206],{"class":1706}," -",[1690,2208,2181],{"class":1702},[1690,2210,2050],{"class":1706},[1690,2212,2187],{"class":1732},[1690,2214,2086],{"class":1706},[1690,2216,2089],{"class":1706},[1690,2218,2219],{"class":1732}," 0.5\n",[1690,2221,2223],{"class":1692,"line":2222},14,[1690,2224,2225],{"class":1702},"        ...\n",[743,2227,2228,2231],{},[761,2229,2230],{},"Kick drum"," - A sine wave that rapidly drops in pitch (150Hz → 50Hz) with a click transient.",[743,2233,2234,2237],{},[761,2235,2236],{},"Snare drum"," - Shell resonance (170Hz with harmonics) + attack transient (450Hz) + filtered noise for the snare wires. Plus a touch of room reverb.",[743,2239,2240,2243],{},[761,2241,2242],{},"Hi-hat"," - White noise mixed with metallic high-frequency tones (6kHz, 8kHz, 10kHz), shaped with ADSR envelopes.",[743,2245,2246],{},"Then we programmed the actual rhythm patterns:",[1619,2248,2250],{"className":1684,"code":2249,"language":1686,"meta":899,"style":899},"RHYTHMS = [\n    (\"4_4\", \"4/4 Time\", \"4/4\", 100, [\n        (0, \"kick\", 0),      # Beat 1\n        (1, \"snare\", -2),    # Beat 2\n        (2, \"kick\", -2),     # Beat 3\n        (3, \"snare\", -2),    # Beat 4\n        # Off-beat hi-hats\n        (0.5, \"hihat\", -6),\n        (1.5, \"hihat\", -6),\n        ...\n    ]),\n    (\"swing\", \"Swing\", \"4/4\", 120, [\n        # Triplet-based timing for swing feel\n        (0.66, \"hihat\", -6),  # Swung eighth\n        ...\n    ]),\n]\n",[1626,2251,2252,2262,2302,2329,2355,2380,2406,2411,2437,2460,2464,2469,2506,2511,2537,2542,2547],{"__ignoreMap":899},[1690,2253,2254,2257,2259],{"class":1692,"line":1693},[1690,2255,2256],{"class":1702},"RHYTHMS ",[1690,2258,1707],{"class":1706},[1690,2260,2261],{"class":1706}," [\n",[1690,2263,2264,2267,2269,2272,2274,2276,2279,2282,2284,2286,2288,2291,2293,2295,2298,2300],{"class":1692,"line":900},[1690,2265,2266],{"class":1706},"    (",[1690,2268,1723],{"class":1706},[1690,2270,2271],{"class":1719},"4_4",[1690,2273,1723],{"class":1706},[1690,2275,1736],{"class":1706},[1690,2277,2278],{"class":1706}," \"",[1690,2280,2281],{"class":1719},"4/4 Time",[1690,2283,1723],{"class":1706},[1690,2285,1736],{"class":1706},[1690,2287,2278],{"class":1706},[1690,2289,2290],{"class":1719},"4/4",[1690,2292,1723],{"class":1706},[1690,2294,1736],{"class":1706},[1690,2296,2297],{"class":1732}," 100",[1690,2299,1736],{"class":1706},[1690,2301,2261],{"class":1706},[1690,2303,2304,2307,2309,2311,2313,2316,2318,2320,2323,2326],{"class":1692,"line":1713},[1690,2305,2306],{"class":1706},"        (",[1690,2308,1733],{"class":1732},[1690,2310,1736],{"class":1706},[1690,2312,2278],{"class":1706},[1690,2314,2315],{"class":1719},"kick",[1690,2317,1723],{"class":1706},[1690,2319,1736],{"class":1706},[1690,2321,2322],{"class":1732}," 0",[1690,2324,2325],{"class":1706},"),",[1690,2327,2328],{"class":1696},"      # Beat 1\n",[1690,2330,2331,2333,2335,2337,2339,2342,2344,2346,2348,2350,2352],{"class":1692,"line":1259},[1690,2332,2306],{"class":1706},[1690,2334,2203],{"class":1732},[1690,2336,1736],{"class":1706},[1690,2338,2278],{"class":1706},[1690,2340,2341],{"class":1719},"snare",[1690,2343,1723],{"class":1706},[1690,2345,1736],{"class":1706},[1690,2347,2206],{"class":1706},[1690,2349,2141],{"class":1732},[1690,2351,2325],{"class":1706},[1690,2353,2354],{"class":1696},"    # Beat 2\n",[1690,2356,2357,2359,2361,2363,2365,2367,2369,2371,2373,2375,2377],{"class":1692,"line":1782},[1690,2358,2306],{"class":1706},[1690,2360,2141],{"class":1732},[1690,2362,1736],{"class":1706},[1690,2364,2278],{"class":1706},[1690,2366,2315],{"class":1719},[1690,2368,1723],{"class":1706},[1690,2370,1736],{"class":1706},[1690,2372,2206],{"class":1706},[1690,2374,2141],{"class":1732},[1690,2376,2325],{"class":1706},[1690,2378,2379],{"class":1696},"     # Beat 3\n",[1690,2381,2382,2384,2387,2389,2391,2393,2395,2397,2399,2401,2403],{"class":1692,"line":1816},[1690,2383,2306],{"class":1706},[1690,2385,2386],{"class":1732},"3",[1690,2388,1736],{"class":1706},[1690,2390,2278],{"class":1706},[1690,2392,2341],{"class":1719},[1690,2394,1723],{"class":1706},[1690,2396,1736],{"class":1706},[1690,2398,2206],{"class":1706},[1690,2400,2141],{"class":1732},[1690,2402,2325],{"class":1706},[1690,2404,2405],{"class":1696},"    # Beat 4\n",[1690,2407,2408],{"class":1692,"line":1849},[1690,2409,2410],{"class":1696},"        # Off-beat hi-hats\n",[1690,2412,2413,2415,2418,2420,2422,2425,2427,2429,2431,2434],{"class":1692,"line":1883},[1690,2414,2306],{"class":1706},[1690,2416,2417],{"class":1732},"0.5",[1690,2419,1736],{"class":1706},[1690,2421,2278],{"class":1706},[1690,2423,2424],{"class":1719},"hihat",[1690,2426,1723],{"class":1706},[1690,2428,1736],{"class":1706},[1690,2430,2206],{"class":1706},[1690,2432,2433],{"class":1732},"6",[1690,2435,2436],{"class":1706},"),\n",[1690,2438,2439,2441,2444,2446,2448,2450,2452,2454,2456,2458],{"class":1692,"line":1913},[1690,2440,2306],{"class":1706},[1690,2442,2443],{"class":1732},"1.5",[1690,2445,1736],{"class":1706},[1690,2447,2278],{"class":1706},[1690,2449,2424],{"class":1719},[1690,2451,1723],{"class":1706},[1690,2453,1736],{"class":1706},[1690,2455,2206],{"class":1706},[1690,2457,2433],{"class":1732},[1690,2459,2436],{"class":1706},[1690,2461,2462],{"class":1692,"line":1943},[1690,2463,2225],{"class":1702},[1690,2465,2466],{"class":1692,"line":2169},[1690,2467,2468],{"class":1706},"    ]),\n",[1690,2470,2471,2473,2475,2478,2480,2482,2484,2487,2489,2491,2493,2495,2497,2499,2502,2504],{"class":1692,"line":2175},[1690,2472,2266],{"class":1706},[1690,2474,1723],{"class":1706},[1690,2476,2477],{"class":1719},"swing",[1690,2479,1723],{"class":1706},[1690,2481,1736],{"class":1706},[1690,2483,2278],{"class":1706},[1690,2485,2486],{"class":1719},"Swing",[1690,2488,1723],{"class":1706},[1690,2490,1736],{"class":1706},[1690,2492,2278],{"class":1706},[1690,2494,2290],{"class":1719},[1690,2496,1723],{"class":1706},[1690,2498,1736],{"class":1706},[1690,2500,2501],{"class":1732}," 120",[1690,2503,1736],{"class":1706},[1690,2505,2261],{"class":1706},[1690,2507,2508],{"class":1692,"line":2193},[1690,2509,2510],{"class":1696},"        # Triplet-based timing for swing feel\n",[1690,2512,2513,2515,2518,2520,2522,2524,2526,2528,2530,2532,2534],{"class":1692,"line":2222},[1690,2514,2306],{"class":1706},[1690,2516,2517],{"class":1732},"0.66",[1690,2519,1736],{"class":1706},[1690,2521,2278],{"class":1706},[1690,2523,2424],{"class":1719},[1690,2525,1723],{"class":1706},[1690,2527,1736],{"class":1706},[1690,2529,2206],{"class":1706},[1690,2531,2433],{"class":1732},[1690,2533,2325],{"class":1706},[1690,2535,2536],{"class":1696},"  # Swung eighth\n",[1690,2538,2540],{"class":1692,"line":2539},15,[1690,2541,2225],{"class":1702},[1690,2543,2545],{"class":1692,"line":2544},16,[1690,2546,2468],{"class":1706},[1690,2548,2550],{"class":1692,"line":2549},17,[1690,2551,2552],{"class":1706},"]\n",[743,2554,2555],{},"8 rhythm patterns, each with its own BPM, time signature, and accent patterns.",[1496,2557,2559],{"id":2558},"pure-tones-for-note-training","Pure Tones for Note Training",[743,2561,2562,2563,1726],{},"For the Notes category, we used ",[761,2564,2565],{},"pydub's sine wave generator",[1619,2567,2569],{"className":1684,"code":2568,"language":1686,"meta":899,"style":899},"NOTES = [\n    (\"c\", \"C\", 261.63),  # C4 (Middle C)\n    (\"d\", \"D\", 293.66),  # D4\n    (\"e\", \"E\", 329.63),  # E4\n    ...\n    (\"c_octave\", \"C (Octave)\", 523.25),  # C5\n]\n\ndef generate_note_audio(frequency, duration_ms):\n    tone = Sine(frequency).to_audio_segment(duration=duration_ms)\n    tone = tone.fade_in(50).fade_out(200)  # Avoid clicks\n    return tone\n",[1626,2570,2571,2580,2610,2640,2670,2675,2705,2709,2713,2732,2763,2797],{"__ignoreMap":899},[1690,2572,2573,2576,2578],{"class":1692,"line":1693},[1690,2574,2575],{"class":1702},"NOTES ",[1690,2577,1707],{"class":1706},[1690,2579,2261],{"class":1706},[1690,2581,2582,2584,2586,2589,2591,2593,2595,2598,2600,2602,2605,2607],{"class":1692,"line":900},[1690,2583,2266],{"class":1706},[1690,2585,1723],{"class":1706},[1690,2587,2588],{"class":1719},"c",[1690,2590,1723],{"class":1706},[1690,2592,1736],{"class":1706},[1690,2594,2278],{"class":1706},[1690,2596,2597],{"class":1719},"C",[1690,2599,1723],{"class":1706},[1690,2601,1736],{"class":1706},[1690,2603,2604],{"class":1732}," 261.63",[1690,2606,2325],{"class":1706},[1690,2608,2609],{"class":1696},"  # C4 (Middle C)\n",[1690,2611,2612,2614,2616,2619,2621,2623,2625,2628,2630,2632,2635,2637],{"class":1692,"line":1713},[1690,2613,2266],{"class":1706},[1690,2615,1723],{"class":1706},[1690,2617,2618],{"class":1719},"d",[1690,2620,1723],{"class":1706},[1690,2622,1736],{"class":1706},[1690,2624,2278],{"class":1706},[1690,2626,2627],{"class":1719},"D",[1690,2629,1723],{"class":1706},[1690,2631,1736],{"class":1706},[1690,2633,2634],{"class":1732}," 293.66",[1690,2636,2325],{"class":1706},[1690,2638,2639],{"class":1696},"  # D4\n",[1690,2641,2642,2644,2646,2649,2651,2653,2655,2658,2660,2662,2665,2667],{"class":1692,"line":1259},[1690,2643,2266],{"class":1706},[1690,2645,1723],{"class":1706},[1690,2647,2648],{"class":1719},"e",[1690,2650,1723],{"class":1706},[1690,2652,1736],{"class":1706},[1690,2654,2278],{"class":1706},[1690,2656,2657],{"class":1719},"E",[1690,2659,1723],{"class":1706},[1690,2661,1736],{"class":1706},[1690,2663,2664],{"class":1732}," 329.63",[1690,2666,2325],{"class":1706},[1690,2668,2669],{"class":1696},"  # E4\n",[1690,2671,2672],{"class":1692,"line":1782},[1690,2673,2674],{"class":1702},"    ...\n",[1690,2676,2677,2679,2681,2684,2686,2688,2690,2693,2695,2697,2700,2702],{"class":1692,"line":1816},[1690,2678,2266],{"class":1706},[1690,2680,1723],{"class":1706},[1690,2682,2683],{"class":1719},"c_octave",[1690,2685,1723],{"class":1706},[1690,2687,1736],{"class":1706},[1690,2689,2278],{"class":1706},[1690,2691,2692],{"class":1719},"C (Octave)",[1690,2694,1723],{"class":1706},[1690,2696,1736],{"class":1706},[1690,2698,2699],{"class":1732}," 523.25",[1690,2701,2325],{"class":1706},[1690,2703,2704],{"class":1696},"  # C5\n",[1690,2706,2707],{"class":1692,"line":1849},[1690,2708,2552],{"class":1706},[1690,2710,2711],{"class":1692,"line":1883},[1690,2712,2117],{"emptyLinePlaceholder":916},[1690,2714,2715,2717,2720,2722,2725,2727,2730],{"class":1692,"line":1913},[1690,2716,1986],{"class":1985},[1690,2718,2719],{"class":1989}," generate_note_audio",[1690,2721,1993],{"class":1706},[1690,2723,2724],{"class":1996},"frequency",[1690,2726,1736],{"class":1706},[1690,2728,2729],{"class":1996}," duration_ms",[1690,2731,2005],{"class":1706},[1690,2733,2734,2737,2739,2742,2744,2746,2749,2752,2754,2757,2759,2761],{"class":1692,"line":1943},[1690,2735,2736],{"class":1702},"    tone ",[1690,2738,1707],{"class":1706},[1690,2740,2741],{"class":1989}," Sine",[1690,2743,1993],{"class":1706},[1690,2745,2724],{"class":1989},[1690,2747,2748],{"class":1706},").",[1690,2750,2751],{"class":1989},"to_audio_segment",[1690,2753,1993],{"class":1706},[1690,2755,2756],{"class":1996},"duration",[1690,2758,1707],{"class":1706},[1690,2760,1997],{"class":1989},[1690,2762,2112],{"class":1706},[1690,2764,2765,2767,2769,2772,2774,2777,2779,2782,2784,2787,2789,2792,2794],{"class":1692,"line":2169},[1690,2766,2736],{"class":1702},[1690,2768,1707],{"class":1706},[1690,2770,2771],{"class":1702}," tone",[1690,2773,1586],{"class":1706},[1690,2775,2776],{"class":1989},"fade_in",[1690,2778,1993],{"class":1706},[1690,2780,2781],{"class":1732},"50",[1690,2783,2748],{"class":1706},[1690,2785,2786],{"class":1989},"fade_out",[1690,2788,1993],{"class":1706},[1690,2790,2791],{"class":1732},"200",[1690,2793,2086],{"class":1706},[1690,2795,2796],{"class":1696},"  # Avoid clicks\n",[1690,2798,2799,2802],{"class":1692,"line":2175},[1690,2800,2801],{"class":2010},"    return",[1690,2803,2804],{"class":1702}," tone\n",[743,2806,2807],{},"Simple, clean, and perfect for ear training.",[1496,2809,2811],{"id":2810},"post-processing-cutting-trimming-normalizing","Post-Processing: Cutting, Trimming, Normalizing",[743,2813,2814],{},"Raw generated audio isn't always game-ready. We wrote additional scripts:",[743,2816,2817,2820],{},[761,2818,2819],{},"Cut scales to ascending only"," - Original scales went up AND down. Too long. We cut them to just the ascending portion:",[1619,2822,2824],{"className":1684,"code":2823,"language":1686,"meta":899,"style":899},"def cut_to_ascending(file_path):\n    audio = AudioSegment.from_mp3(file_path)\n    half_point = len(audio) // 2\n    ascending_only = audio[:half_point]\n    ascending_only.export(file_path, format=\"mp3\")\n",[1626,2825,2826,2840,2861,2884,2902],{"__ignoreMap":899},[1690,2827,2828,2830,2833,2835,2838],{"class":1692,"line":1693},[1690,2829,1986],{"class":1985},[1690,2831,2832],{"class":1989}," cut_to_ascending",[1690,2834,1993],{"class":1706},[1690,2836,2837],{"class":1996},"file_path",[1690,2839,2005],{"class":1706},[1690,2841,2842,2845,2847,2850,2852,2855,2857,2859],{"class":1692,"line":900},[1690,2843,2844],{"class":1702},"    audio ",[1690,2846,1707],{"class":1706},[1690,2848,2849],{"class":1702}," AudioSegment",[1690,2851,1586],{"class":1706},[1690,2853,2854],{"class":1989},"from_mp3",[1690,2856,1993],{"class":1706},[1690,2858,2837],{"class":1989},[1690,2860,2112],{"class":1706},[1690,2862,2863,2866,2868,2871,2873,2876,2878,2881],{"class":1692,"line":1713},[1690,2864,2865],{"class":1702},"    half_point ",[1690,2867,1707],{"class":1706},[1690,2869,2870],{"class":1989}," len",[1690,2872,1993],{"class":1706},[1690,2874,2875],{"class":1989},"audio",[1690,2877,2086],{"class":1706},[1690,2879,2880],{"class":1706}," //",[1690,2882,2883],{"class":1732}," 2\n",[1690,2885,2886,2889,2891,2894,2897,2900],{"class":1692,"line":1259},[1690,2887,2888],{"class":1702},"    ascending_only ",[1690,2890,1707],{"class":1706},[1690,2892,2893],{"class":1702}," audio",[1690,2895,2896],{"class":1706},"[:",[1690,2898,2899],{"class":1702},"half_point",[1690,2901,2552],{"class":1706},[1690,2903,2904,2907,2909,2912,2914,2916,2918,2921,2923,2925,2928,2930],{"class":1692,"line":1782},[1690,2905,2906],{"class":1702},"    ascending_only",[1690,2908,1586],{"class":1706},[1690,2910,2911],{"class":1989},"export",[1690,2913,1993],{"class":1706},[1690,2915,2837],{"class":1989},[1690,2917,1736],{"class":1706},[1690,2919,2920],{"class":1996}," format",[1690,2922,1707],{"class":1706},[1690,2924,1723],{"class":1706},[1690,2926,2927],{"class":1719},"mp3",[1690,2929,1723],{"class":1706},[1690,2931,2112],{"class":1706},[743,2933,2934,2937],{},[761,2935,2936],{},"Trim chord arpeggios"," - Some chords had unwanted arpeggio intros. FFmpeg to the rescue:",[1619,2939,2943],{"className":2940,"code":2941,"language":2942,"meta":899,"style":899},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","ffmpeg -y -i chord.mp3 -ss [start_time] -acodec libmp3lame chord_trimmed.mp3\n","bash",[1626,2944,2945],{"__ignoreMap":899},[1690,2946,2947,2951,2954,2957,2960,2963],{"class":1692,"line":1693},[1690,2948,2950],{"class":2949},"sBMFI","ffmpeg",[1690,2952,2953],{"class":1719}," -y",[1690,2955,2956],{"class":1719}," -i",[1690,2958,2959],{"class":1719}," chord.mp3",[1690,2961,2962],{"class":1719}," -ss",[1690,2964,2965],{"class":1702}," [start_time] -acodec libmp3lame chord_trimmed.mp3\n",[743,2967,2968,2971],{},[761,2969,2970],{},"Batch update SVG colors"," - Our chord diagrams needed color adjustments to match the app theme. Regex-based batch processing:",[1619,2973,2975],{"className":1684,"code":2974,"language":1686,"meta":899,"style":899},"COLOR_REPLACEMENTS = {\n    '#f3f8f3': '#000000',  # Inactive keys: light → dark\n    '#b3cc57': '#C6F222',  # Active keys: old green → MTL lime\n}\n",[1626,2976,2977,2986,3012,3035],{"__ignoreMap":899},[1690,2978,2979,2982,2984],{"class":1692,"line":1693},[1690,2980,2981],{"class":1702},"COLOR_REPLACEMENTS ",[1690,2983,1707],{"class":1706},[1690,2985,1710],{"class":1706},[1690,2987,2988,2991,2994,2997,2999,3002,3005,3007,3009],{"class":1692,"line":900},[1690,2989,2990],{"class":1706},"    '",[1690,2992,2993],{"class":1719},"#f3f8f3",[1690,2995,2996],{"class":1706},"'",[1690,2998,1726],{"class":1706},[1690,3000,3001],{"class":1706}," '",[1690,3003,3004],{"class":1719},"#000000",[1690,3006,2996],{"class":1706},[1690,3008,1736],{"class":1706},[1690,3010,3011],{"class":1696},"  # Inactive keys: light → dark\n",[1690,3013,3014,3016,3019,3021,3023,3025,3028,3030,3032],{"class":1692,"line":1713},[1690,3015,2990],{"class":1706},[1690,3017,3018],{"class":1719},"#b3cc57",[1690,3020,2996],{"class":1706},[1690,3022,1726],{"class":1706},[1690,3024,3001],{"class":1706},[1690,3026,3027],{"class":1719},"#C6F222",[1690,3029,2996],{"class":1706},[1690,3031,1736],{"class":1706},[1690,3033,3034],{"class":1696},"  # Active keys: old green → MTL lime\n",[1690,3036,3037],{"class":1692,"line":1259},[1690,3038,1946],{"class":1706},[1496,3040,3042],{"id":3041},"the-numbers","The Numbers",[743,3044,3045],{},"By the end, our content pipeline generated:",[1168,3047,3048,3061],{},[1171,3049,3050],{},[1174,3051,3052,3055,3058],{},[1177,3053,3054],{},"Category",[1177,3056,3057],{},"Files",[1177,3059,3060],{},"Method",[1182,3062,3063,3074,3084,3095,3106,3117],{},[1174,3064,3065,3068,3071],{},[1187,3066,3067],{},"Chords",[1187,3069,3070],{},"168 audio + 168 SVG",[1187,3072,3073],{},"MIDI → FluidSynth → FFmpeg",[1174,3075,3076,3079,3082],{},[1187,3077,3078],{},"Scales",[1187,3080,3081],{},"14 audio + 14 SVG",[1187,3083,3073],{},[1174,3085,3086,3089,3092],{},[1187,3087,3088],{},"Notes",[1187,3090,3091],{},"8 audio + 8 SVG",[1187,3093,3094],{},"pydub sine wave synthesis",[1174,3096,3097,3100,3103],{},[1187,3098,3099],{},"Rhythms",[1187,3101,3102],{},"8 audio + 8 PNG",[1187,3104,3105],{},"Mathematical drum synthesis",[1174,3107,3108,3111,3114],{},[1187,3109,3110],{},"Animals",[1187,3112,3113],{},"18 audio + 18 PNG",[1187,3115,3116],{},"Curated library",[1174,3118,3119,3122,3124],{},[1187,3120,3121],{},"Instruments",[1187,3123,3102],{},[1187,3125,3126],{},"Curated samples",[743,3128,3129,3132],{},[761,3130,3131],{},"Total: 300+ assets",", mostly generated programmatically.",[1496,3134,3136],{"id":3135},"why-this-matters","Why This Matters",[743,3138,3139],{},"Could we have licensed a chord library? Sure. But:",[1591,3141,3142,3148,3154,3160],{},[758,3143,3144,3147],{},[761,3145,3146],{},"Consistency"," - Every chord sounds identical in timbre, velocity, duration",[758,3149,3150,3153],{},[761,3151,3152],{},"Customization"," - Need a longer sustain? Change one variable, regenerate",[758,3155,3156,3159],{},[761,3157,3158],{},"No licensing headaches"," - We own every bit of audio",[758,3161,3162,3165],{},[761,3163,3164],{},"Educational value"," - We actually understand what we're teaching",[743,3167,3168,3169,1586],{},"Plus, writing a drum synthesizer from scratch is just ",[1434,3170,3171],{},"fun",[1463,3173],{},[750,3175,3177],{"id":3176},"the-categories-more-than-just-music","The Categories: More Than Just Music",[743,3179,3180],{},"While we started with music education in mind, we quickly realized the concept works for much more.",[1496,3182,3184],{"id":3183},"musical-categories","Musical Categories",[961,3186,1447,3190,3195,3196],{"className":3187},[1443,1444,3188,3189],"gap-8","items-start",[1453,3191],{"src":3192,"alt":3193,"className":3194},"/images/blog/musictechlab_blog_how-we-built-memosonic_inline_3.webp","Category selection",[1458],"\n   ",[1453,3197],{"src":3198,"alt":3199,"className":3200},"/images/blog/musictechlab_blog_how-we-built-memosonic_inline_4.webp","Chord diagram",[1458],[743,3202,3203,3205],{},[761,3204,3067],{}," - Can you tell the difference between a major and minor chord? What about diminished vs. augmented? This category trains your ear to recognize the emotional quality of different chord types.",[743,3207,3208,3210],{},[761,3209,3078],{}," - From the bright C Major to the melancholic A Natural Minor, players learn to identify scales by their unique character.",[743,3212,3213,3215],{},[761,3214,3088],{}," - Perfect for beginners learning to identify individual pitches on the musical staff.",[743,3217,3218,3220],{},[761,3219,3099],{}," - 3/4 waltz? 4/4 rock beat? Syncopated funk? Train your rhythmic ear.",[1496,3222,3224],{"id":3223},"beyond-music","Beyond Music",[743,3226,3227,3229],{},[761,3228,3110],{}," - 18 animal sounds, from the roar of a lion to the chirp of a bird. Perfect for younger kids or anyone who just wants a fun challenge.",[743,3231,3232,3234],{},[761,3233,3121],{}," - Can you distinguish a trumpet from a saxophone? A violin from a cello? Harder than you'd think!",[1463,3236],{},[750,3238,3240],{"id":3239},"the-game-modes-flexibility-matters","The Game Modes: Flexibility Matters",[743,3242,3243],{},"Not everyone learns the same way. That's why MemoSonic offers two distinct game modes:",[1496,3245,3247],{"id":3246},"memosonic-mode-sound-first","Memosonic Mode (Sound-First)",[743,3249,3250],{},"This is the heart of the app. Tap a card, hear a sound. Remember that sound. Find its match.",[1619,3252,3255],{"className":3253,"code":3254,"language":1624},[1622],"1. Tap card → Hear sound (no visual)\n2. Tap another card → Hear second sound\n3. Match? → Cards reveal and stay\n4. No match? → Cards flip back, remember the sounds!\n",[1626,3256,3254],{"__ignoreMap":899},[961,3258,1447,3260,1447,3263,1447,3268],{"className":3259},[1443,1444,1445,1446],[961,3261],{"className":3262},[1451],[1453,3264],{"src":3265,"alt":3266,"className":3267},"/images/blog/musictechlab_blog_how-we-built-memosonic_inline_5.webp","Memosonic mode",[1458],[961,3269],{"className":3270},[1451],[1496,3272,3274],{"id":3273},"memo-classic-mode-visual-first","Memo Classic Mode (Visual-First)",[743,3276,3277],{},"For those who want a traditional experience, or as a comparison to understand how much harder audio matching is!",[1619,3279,3282],{"className":3280,"code":3281,"language":1624},[1622],"1. Tap card → Image briefly appears (1 second)\n2. Tap another card → Second image appears\n3. Match? → Cards stay revealed\n4. No match? → Images hide, remember positions!\n",[1626,3283,3281],{"__ignoreMap":899},[1463,3285],{},[750,3287,3289],{"id":3288},"difficulty-levels-progressive-challenge","Difficulty Levels: Progressive Challenge",[743,3291,3292],{},"We designed three difficulty levels, each carefully balanced:",[1168,3294,3295,3311],{},[1171,3296,3297],{},[1174,3298,3299,3302,3305,3308],{},[1177,3300,3301],{},"Level",[1177,3303,3304],{},"Cards",[1177,3306,3307],{},"Pairs",[1177,3309,3310],{},"Estimated Time",[1182,3312,3313,3325,3338],{},[1174,3314,3315,3318,3320,3322],{},[1187,3316,3317],{},"Easy",[1187,3319,2433],{},[1187,3321,2386],{},[1187,3323,3324],{},"2-3 minutes",[1174,3326,3327,3330,3333,3335],{},[1187,3328,3329],{},"Normal",[1187,3331,3332],{},"12",[1187,3334,2433],{},[1187,3336,3337],{},"5-7 minutes",[1174,3339,3340,3343,3346,3349],{},[1187,3341,3342],{},"Hard",[1187,3344,3345],{},"20",[1187,3347,3348],{},"10",[1187,3350,3351],{},"10-15 minutes",[961,3353,1447,3355,1447,3358,1447,3363],{"className":3354},[1443,1444,1445,1446],[961,3356],{"className":3357},[1451],[1453,3359],{"src":3360,"alt":3361,"className":3362},"/images/blog/musictechlab_blog_how-we-built-memosonic_inline_7.webp","Level selection",[1458],[961,3364],{"className":3365},[1451],[743,3367,3368],{},"The jump from 6 to 12 cards isn't just \"twice as hard\" - it's exponentially more challenging. Your brain can hold about 7 items in working memory. At 12 cards, you're constantly pushing that limit.",[743,3370,3371],{},"At 20 cards? It's a real workout.",[1463,3373],{},[750,3375,3377],{"id":3376},"accessibility-designing-for-everyone","Accessibility: Designing for Everyone",[743,3379,3380],{},"Here's where it gets important.",[1496,3382,3384],{"id":3383},"the-visually-impaired-perspective","The Visually Impaired Perspective",[743,3386,3387],{},"Traditional memory games are impossible for blind or visually impaired players. The entire mechanic relies on seeing and remembering visual positions.",[743,3389,3390],{},[761,3391,3392],{},"MemoSonic flips this on its head.",[743,3394,3395],{},"In Memosonic mode, vision is secondary. You're listening, remembering sounds, matching audio. A visually impaired player can:",[1591,3397,3398,3401,3404,3407],{},[758,3399,3400],{},"Navigate the grid using screen reader or spatial memory",[758,3402,3403],{},"Tap cards to hear sounds",[758,3405,3406],{},"Match based purely on audio memory",[758,3408,3409],{},"Receive audio feedback on success/failure",[961,3411,1447,3413,1447,3416,1447,3421],{"className":3412},[1443,1444,1445,1446],[961,3414],{"className":3415},[1451],[1453,3417],{"src":3418,"alt":3419,"className":3420},"/images/blog/musictechlab_blog_how-we-built-memosonic_inline_8.webp","Game completion",[1458],[961,3422],{"className":3423},[1451],[1496,3425,3427],{"id":3426},"design-decisions-for-accessibility","Design Decisions for Accessibility",[743,3429,3430],{},"We made several deliberate choices:",[743,3432,3433],{},[761,3434,3435],{},"High Contrast UI",[755,3437,3438,3441,3444,3447],{},[758,3439,3440],{},"Dark background (#0E0F11)",[758,3442,3443],{},"Bright lime yellow accents (#C6F222)",[758,3445,3446],{},"Clear white text",[758,3448,3449],{},"No reliance on color alone for meaning",[743,3451,3452],{},[761,3453,3454],{},"Large Touch Targets",[755,3456,3457,3460,3463],{},[758,3458,3459],{},"Cards are generously sized",[758,3461,3462],{},"Buttons have ample padding",[758,3464,3465],{},"No precision tapping required",[743,3467,3468],{},[761,3469,3470],{},"Audio Feedback",[755,3472,3473,3476,3479],{},[758,3474,3475],{},"Every interaction has sound",[758,3477,3478],{},"Success/failure clearly distinguishable",[758,3480,3481],{},"No silent failures",[743,3483,3484],{},[761,3485,3486],{},"Simple Navigation",[755,3488,3489,3492,3495],{},[758,3490,3491],{},"Linear flow: Home → Category → Level → Game",[758,3493,3494],{},"Back button always available",[758,3496,3497],{},"No complex gestures required",[1463,3499],{},[750,3501,3503],{"id":3502},"the-technical-deep-dive-for-fellow-developers","The Technical Deep Dive (For Fellow Developers)",[1496,3505,3507],{"id":3506},"project-architecture","Project Architecture",[1619,3509,3512],{"className":3510,"code":3511,"language":1624},[1622],"lib/\n├── main.dart                    # Entry point, splash screen\n├── core/\n│   ├── routes.dart              # GoRouter navigation\n│   ├── theme.dart               # Material 3 dark theme\n│   └── game_utils.dart          # Category definitions, game data\n├── features/\n│   ├── home_screen.dart         # Category selection grid\n│   ├── level_screen.dart        # Difficulty picker\n│   ├── game_screen.dart         # Main gameplay\n│   └── settings_screen.dart     # Settings hub\n└── widgets/\n    └── app_logo.dart            # Reusable logo component\n",[1626,3513,3511],{"__ignoreMap":899},[1496,3515,3517],{"id":3516},"key-dependencies","Key Dependencies",[1619,3519,3523],{"className":3520,"code":3521,"language":3522,"meta":899,"style":899},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","dependencies:\n  flutter_riverpod: ^3.0.1    # State management\n  go_router: ^16.3.0          # Navigation\n  just_audio: ^0.10.5         # Primary audio engine\n  audioplayers: ^6.1.0        # Alternative audio playback\n  flutter_svg: ^2.0.10        # SVG rendering for diagrams\n  shared_preferences: ^2.5.3  # Local settings storage\n","yaml",[1626,3524,3525,3532,3545,3558,3571,3584,3597],{"__ignoreMap":899},[1690,3526,3527,3530],{"class":1692,"line":1693},[1690,3528,3529],{"class":2150},"dependencies",[1690,3531,2190],{"class":1706},[1690,3533,3534,3537,3539,3542],{"class":1692,"line":900},[1690,3535,3536],{"class":2150},"  flutter_riverpod",[1690,3538,1726],{"class":1706},[1690,3540,3541],{"class":1719}," ^3.0.1",[1690,3543,3544],{"class":1696},"    # State management\n",[1690,3546,3547,3550,3552,3555],{"class":1692,"line":1713},[1690,3548,3549],{"class":2150},"  go_router",[1690,3551,1726],{"class":1706},[1690,3553,3554],{"class":1719}," ^16.3.0",[1690,3556,3557],{"class":1696},"          # Navigation\n",[1690,3559,3560,3563,3565,3568],{"class":1692,"line":1259},[1690,3561,3562],{"class":2150},"  just_audio",[1690,3564,1726],{"class":1706},[1690,3566,3567],{"class":1719}," ^0.10.5",[1690,3569,3570],{"class":1696},"         # Primary audio engine\n",[1690,3572,3573,3576,3578,3581],{"class":1692,"line":1782},[1690,3574,3575],{"class":2150},"  audioplayers",[1690,3577,1726],{"class":1706},[1690,3579,3580],{"class":1719}," ^6.1.0",[1690,3582,3583],{"class":1696},"        # Alternative audio playback\n",[1690,3585,3586,3589,3591,3594],{"class":1692,"line":1816},[1690,3587,3588],{"class":2150},"  flutter_svg",[1690,3590,1726],{"class":1706},[1690,3592,3593],{"class":1719}," ^2.0.10",[1690,3595,3596],{"class":1696},"        # SVG rendering for diagrams\n",[1690,3598,3599,3602,3604,3607],{"class":1692,"line":1849},[1690,3600,3601],{"class":2150},"  shared_preferences",[1690,3603,1726],{"class":1706},[1690,3605,3606],{"class":1719}," ^2.5.3",[1690,3608,3609],{"class":1696},"  # Local settings storage\n",[1496,3611,3613],{"id":3612},"the-card-flip-animation","The Card Flip Animation",[743,3615,3616],{},"One of the most satisfying parts of the app is the card flip animation. Here's the approach:",[1619,3618,3622],{"className":3619,"code":3620,"language":3621,"meta":899,"style":899},"language-dart shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","// Simplified card flip logic\nAnimatedBuilder(\n  animation: _flipAnimation,\n  builder: (context, child) {\n    final angle = _flipAnimation.value * pi;\n    final isFront = angle \u003C pi / 2;\n\n    return Transform(\n      transform: Matrix4.identity()\n        ..setEntry(3, 2, 0.001)  // Perspective\n        ..rotateY(angle),\n      alignment: Alignment.center,\n      child: isFront ? _buildFrontFace() : _buildBackFace(),\n    );\n  },\n)\n","dart",[1626,3623,3624,3629,3637,3650,3665,3690,3713,3717,3726,3744,3771,3783,3800,3828,3835,3842],{"__ignoreMap":899},[1690,3625,3626],{"class":1692,"line":1693},[1690,3627,3628],{"class":1696},"// Simplified card flip logic\n",[1690,3630,3631,3634],{"class":1692,"line":900},[1690,3632,3633],{"class":2949},"AnimatedBuilder",[1690,3635,3636],{"class":1702},"(\n",[1690,3638,3639,3642,3644,3647],{"class":1692,"line":1713},[1690,3640,3641],{"class":1702},"  animation",[1690,3643,1726],{"class":1706},[1690,3645,3646],{"class":1702}," _flipAnimation",[1690,3648,3649],{"class":1706},",\n",[1690,3651,3652,3655,3657,3660,3662],{"class":1692,"line":1259},[1690,3653,3654],{"class":1702},"  builder",[1690,3656,1726],{"class":1706},[1690,3658,3659],{"class":1702}," (context",[1690,3661,1736],{"class":1706},[1690,3663,3664],{"class":1702}," child) {\n",[1690,3666,3667,3670,3673,3675,3677,3679,3682,3684,3687],{"class":1692,"line":1782},[1690,3668,3669],{"class":1985},"    final",[1690,3671,3672],{"class":1702}," angle ",[1690,3674,1707],{"class":1706},[1690,3676,3646],{"class":1702},[1690,3678,1586],{"class":1706},[1690,3680,3681],{"class":1702},"value ",[1690,3683,2106],{"class":1706},[1690,3685,3686],{"class":1702}," pi",[1690,3688,3689],{"class":1706},";\n",[1690,3691,3692,3694,3697,3699,3701,3703,3706,3708,3711],{"class":1692,"line":1816},[1690,3693,3669],{"class":1985},[1690,3695,3696],{"class":1702}," isFront ",[1690,3698,1707],{"class":1706},[1690,3700,3672],{"class":1702},[1690,3702,2184],{"class":1706},[1690,3704,3705],{"class":1702}," pi ",[1690,3707,2050],{"class":1706},[1690,3709,3710],{"class":1732}," 2",[1690,3712,3689],{"class":1706},[1690,3714,3715],{"class":1692,"line":1849},[1690,3716,2117],{"emptyLinePlaceholder":916},[1690,3718,3719,3721,3724],{"class":1692,"line":1883},[1690,3720,2801],{"class":2010},[1690,3722,3723],{"class":2949}," Transform",[1690,3725,3636],{"class":1702},[1690,3727,3728,3731,3733,3736,3738,3741],{"class":1692,"line":1913},[1690,3729,3730],{"class":1702},"      transform",[1690,3732,1726],{"class":1706},[1690,3734,3735],{"class":2949}," Matrix4",[1690,3737,1586],{"class":1706},[1690,3739,3740],{"class":1989},"identity",[1690,3742,3743],{"class":1702},"()\n",[1690,3745,3746,3749,3752,3754,3756,3758,3760,3762,3765,3768],{"class":1692,"line":1943},[1690,3747,3748],{"class":1706},"        ..",[1690,3750,3751],{"class":1989},"setEntry",[1690,3753,1993],{"class":1702},[1690,3755,2386],{"class":1732},[1690,3757,1736],{"class":1706},[1690,3759,3710],{"class":1732},[1690,3761,1736],{"class":1706},[1690,3763,3764],{"class":1732}," 0.001",[1690,3766,3767],{"class":1702},")  ",[1690,3769,3770],{"class":1696},"// Perspective\n",[1690,3772,3773,3775,3778,3781],{"class":1692,"line":2169},[1690,3774,3748],{"class":1706},[1690,3776,3777],{"class":1989},"rotateY",[1690,3779,3780],{"class":1702},"(angle)",[1690,3782,3649],{"class":1706},[1690,3784,3785,3788,3790,3793,3795,3798],{"class":1692,"line":2175},[1690,3786,3787],{"class":1702},"      alignment",[1690,3789,1726],{"class":1706},[1690,3791,3792],{"class":2949}," Alignment",[1690,3794,1586],{"class":1706},[1690,3796,3797],{"class":1702},"center",[1690,3799,3649],{"class":1706},[1690,3801,3802,3805,3807,3809,3812,3815,3818,3820,3823,3826],{"class":1692,"line":2193},[1690,3803,3804],{"class":1702},"      child",[1690,3806,1726],{"class":1706},[1690,3808,3696],{"class":1702},[1690,3810,3811],{"class":1706},"?",[1690,3813,3814],{"class":1989}," _buildFrontFace",[1690,3816,3817],{"class":1702},"() ",[1690,3819,1726],{"class":1706},[1690,3821,3822],{"class":1989}," _buildBackFace",[1690,3824,3825],{"class":1702},"()",[1690,3827,3649],{"class":1706},[1690,3829,3830,3833],{"class":1692,"line":2222},[1690,3831,3832],{"class":1702},"    )",[1690,3834,3689],{"class":1706},[1690,3836,3837,3840],{"class":1692,"line":2539},[1690,3838,3839],{"class":1702},"  }",[1690,3841,3649],{"class":1706},[1690,3843,3844],{"class":1692,"line":2544},[1690,3845,2112],{"class":1702},[743,3847,3848,3849,3852],{},"The trick is the perspective transform (",[1626,3850,3851],{},"setEntry(3, 2, 0.001)",") - it gives that satisfying 3D effect without being distracting.",[1463,3854],{},[750,3856,3858],{"id":3857},"lessons-learned","Lessons Learned",[1496,3860,3862],{"id":3861},"_1-audio-latency-is-everything","1. Audio Latency is Everything",[743,3864,3865],{},"In a sound-based game, even tiny delays feel wrong. We spent weeks optimizing audio playback to ensure instant response.",[1496,3867,3869],{"id":3868},"_2-kids-are-brutally-honest-testers","2. Kids Are Brutally Honest Testers",[743,3871,3872],{},"Our first playtest with actual children revealed:",[755,3874,3875,3878,3881],{},[758,3876,3877],{},"\"Why is this taking so long?\" (loading screen was 2 seconds)",[758,3879,3880],{},"\"I already heard that one!\" (audio caching issue)",[758,3882,3883],{},"\"This is too easy!\" (we added Hard mode)",[1496,3885,3887],{"id":3886},"_3-accessibility-isnt-an-afterthought","3. Accessibility Isn't an Afterthought",[743,3889,3890],{},"Building for accessibility from day one is 10x easier than retrofitting. The decisions we made early (audio-first gameplay, high contrast, large targets) paid off.",[1496,3892,3894],{"id":3893},"_4-simple-beats-complex","4. Simple Beats Complex",[743,3896,3897],{},"Our first design had:",[755,3899,3900,3903,3906,3909],{},[758,3901,3902],{},"User accounts",[758,3904,3905],{},"Leaderboards",[758,3907,3908],{},"Achievement systems",[758,3910,3911],{},"Daily challenges",[743,3913,3914],{},"We cut all of it. The core experience - match sounds, train your ear - didn't need any of that. It needed to work flawlessly.",[1463,3916],{},[750,3918,3920],{"id":3919},"whats-next-you-tell-us","What's Next? You Tell Us!",[743,3922,3923,3924,1586],{},"We have a bunch of ideas brewing for the next iteration. But here's the thing - ",[761,3925,3926],{},"we'd rather build what you actually want",[743,3928,3929],{},"Take a look at what we're considering:",[743,3931,3932],{},[761,3933,3934],{},"More Categories",[755,3936,3937,3940,3943],{},[758,3938,3939],{},"🐦 Bird songs (nature education)",[758,3941,3942],{},"🌍 World languages (basic vocabulary)",[758,3944,3945],{},"🎼 Famous melodies (classical music education)",[743,3947,3948],{},[761,3949,3950],{},"Enhanced Accessibility",[755,3952,3953,3956,3959],{},[758,3954,3955],{},"🔊 Full VoiceOver/TalkBack support",[758,3957,3958],{},"📳 Haptic feedback for matches",[758,3960,3961],{},"🎧 Audio descriptions for all UI elements",[743,3963,3964],{},[761,3965,3966],{},"Multiplayer Mode",[755,3968,3969,3972,3975],{},[758,3970,3971],{},"👥 Turn-based competition",[758,3973,3974],{},"⚡ Who can match faster?",[758,3976,3977],{},"🏠 Family game night feature",[743,3979,3980],{},[761,3981,3982],{},"Something else entirely?",[743,3984,3985],{},"Maybe you're a music teacher who needs specific intervals training. Maybe you work with visually impaired students and have insights we haven't considered. Maybe your kid is obsessed with dinosaurs and you want dinosaur sounds.",[743,3987,3988],{},[761,3989,3990],{},"We're listening.",[743,3992,3993,3994,3998],{},"Drop us a line at ",[886,3995,3997],{"href":3996},"mailto:support@musictechlab.io","support@musictechlab.io"," and tell us:",[755,4000,4001,4004,4007],{},[758,4002,4003],{},"Which feature would you use most?",[758,4005,4006],{},"What's missing that would make MemoSonic perfect for you?",[758,4008,4009],{},"Any category ideas we haven't thought of?",[743,4011,4012],{},"The best features come from real users with real needs. Don't be shy - your idea might end up in the next update.",[1463,4014],{},[750,4016,4018],{"id":4017},"the-dream-memosonic-as-a-physical-toy","The Dream: MemoSonic as a Physical Toy",[743,4020,4021],{},"Here's an idea that won't leave our heads...",[743,4023,4024],{},[761,4025,4026],{},"What if MemoSonic wasn't just an app, but a physical toy you could hold in your hands?",[961,4028,1447,4030,1447,4033,1447,4038],{"className":4029},[1443,1444,1445,1446],[961,4031],{"className":4032},[1451],[1453,4034],{"src":4035,"alt":4036,"className":4037},"/images/blog/musictechlab_blog_how-we-built-memosonic_inline_9.webp","Hardware concept",[1458],[961,4039],{"className":4040},[1451],[1496,4042,4044],{"id":4043},"the-vision","The Vision",[743,4046,4047],{},"Picture a compact device with a grid of large, tactile buttons. Each button:",[755,4049,4050,4053,4056,4059],{},[758,4051,4052],{},"Lights up with RGB LEDs",[758,4054,4055],{},"Plays a sound when pressed",[758,4057,4058],{},"Has a satisfying click",[758,4060,4061],{},"Is large enough for small hands (and accessible for everyone)",[743,4063,4064],{},"An 8×8 matrix would give us 64 buttons - enough for complex games while keeping each button big enough to press comfortably:",[1619,4066,4069],{"className":4067,"code":4068,"language":1624},[1622],"[🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘]\n[🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘]\n[🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘]\n[🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘]\n[🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘]\n[🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘]\n[🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘]\n[🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘] [🔘]\n",[1626,4070,4068],{"__ignoreMap":899},[743,4072,4073],{},"Easy mode? Use a 3×2 section. Hard mode? The whole grid lights up.",[1496,4075,4077],{"id":4076},"why-does-this-idea-excite-us","Why Does This Idea Excite Us?",[743,4079,4080,4083],{},[761,4081,4082],{},"Screen-free play."," Parents increasingly want toys that don't involve staring at a screen. A physical MemoSonic would sit on the kitchen table, travel in a backpack, work without WiFi.",[743,4085,4086,4089,4090,4093],{},[761,4087,4088],{},"True tactile accessibility."," For visually impaired users, physical buttons in fixed positions are ",[1434,4091,4092],{},"far"," easier to navigate than a touchscreen. You can feel your way around. Build muscle memory. No screen reader required.",[743,4095,4096,4099],{},[761,4097,4098],{},"Multiplayer without devices."," Gather around the table. Take turns. Compete. No \"pass the phone\" awkwardness.",[743,4101,4102,4105],{},[761,4103,4104],{},"Durability."," Kids are rough. A well-designed hardware toy can survive drops, spills, and sibling conflicts.",[1496,4107,4109],{"id":4108},"what-it-could-look-like-technically","What It Could Look Like Technically",[743,4111,4112],{},"If we were to build this, we'd probably explore:",[755,4114,4115,4121,4127,4133,4139,4145],{},[758,4116,4117,4120],{},[761,4118,4119],{},"ESP32 or Raspberry Pi Pico"," as the brain",[758,4122,4123,4126],{},[761,4124,4125],{},"I2S audio"," for quality sound output",[758,4128,4129,4132],{},[761,4130,4131],{},"NeoPixel/WS2812B"," LEDs for button illumination",[758,4134,4135,4138],{},[761,4136,4137],{},"Rechargeable battery"," with USB-C charging",[758,4140,4141,4144],{},[761,4142,4143],{},"SD card slot"," for custom sound packs",[758,4146,4147,4150],{},[761,4148,4149],{},"Bluetooth LE"," for optional app companion (stats, new sounds)",[743,4152,4153],{},"The app and hardware could even sync - unlock new sounds on the device by mastering them in the app first.",[1496,4155,4157],{"id":4156},"the-accessibility-angle","The Accessibility Angle",[743,4159,4160],{},"This is where it gets really exciting.",[743,4162,4163],{},"For a blind child, a touchscreen memory game is possible but challenging. A physical device with buttons in consistent positions? That's a game they can master as well as any sighted player. Maybe better - they've been training their auditory memory their whole life.",[743,4165,4166],{},"We imagine:",[755,4168,4169,4175,4181,4187],{},[758,4170,4171,4174],{},[761,4172,4173],{},"Raised symbols"," on each button for tactile identification",[758,4176,4177,4180],{},[761,4178,4179],{},"Audio position announcements"," (\"Button 3\" when pressed)",[758,4182,4183,4186],{},[761,4184,4185],{},"Haptic feedback"," for matches and mismatches",[758,4188,4189,4192],{},[761,4190,4191],{},"Braille labeling"," on the device",[1496,4194,4196],{"id":4195},"is-this-something-youd-want","Is This Something You'd Want?",[743,4198,4199],{},"Right now, this is just an idea - a dream sketched on whiteboards and discussed over coffee. We haven't built a prototype yet. We're not sure if we will.",[743,4201,4202],{},[761,4203,4204],{},"But we could, if there's real interest.",[743,4206,4207],{},"Would you buy a physical MemoSonic for your kids? For a classroom? For a visually impaired family member? Would you back it on Kickstarter?",[743,4209,4210,4213],{},[761,4211,4212],{},"Let us know."," If enough people say \"yes, build this thing!\" - we just might.",[743,4215,4216,4217,4219],{},"📧 ",[886,4218,3997],{"href":3996}," - subject line: \"Hardware MemoSonic\"",[743,4221,4222],{},[1434,4223,4224],{},"If the response is strong enough, we'll start prototyping and document the entire journey. Stay tuned.",[1463,4226],{},[750,4228,4230],{"id":4229},"try-it-yourself","Try It Yourself",[743,4232,4233],{},"MemoSonic is available now:",[755,4235,4236,4247,4257],{},[758,4237,4238,4241,4242],{},[761,4239,4240],{},"Web",": ",[886,4243,4246],{"href":4244,"rel":4245},"https://memosonic.musictechlab.io/",[1340],"memosonic.musictechlab.io",[758,4248,4249,4241,4252],{},[761,4250,4251],{},"iOS",[886,4253,4256],{"href":4254,"rel":4255},"https://apps.apple.com/pl/app/memosonic/id6743499949",[1340],"App Store",[758,4258,4259,4241,4262],{},[761,4260,4261],{},"Android",[886,4263,4266],{"href":4264,"rel":4265},"https://play.google.com/store/apps/details?id=com.memosonic.app",[1340],"Google Play",[743,4268,4269],{},"Whether you're a music teacher looking for ear training tools, a parent wanting educational screen time, or someone who just wants to see if they can beat their kids at a memory game - give it a try.",[1463,4271],{},[750,4273,4275],{"id":4274},"the-bigger-picture","The Bigger Picture",[743,4277,4278],{},"MemoSonic started as a rainy day experiment. It became something more - a proof that:",[1591,4280,4281,4287,4293,4299],{},[758,4282,4283,4286],{},[761,4284,4285],{},"Learning doesn't have to look like learning."," The best educational tools feel like play.",[758,4288,4289,4292],{},[761,4290,4291],{},"Accessibility opens doors."," By designing for sound-first gameplay, we accidentally created something that works for people we hadn't initially considered.",[758,4294,4295,4298],{},[761,4296,4297],{},"Simple ideas can have big impact."," Flip cards, match sounds. That's it. But the applications - music education, auditory training, inclusive gaming - are vast.",[758,4300,4301,4304],{},[761,4302,4303],{},"Software is just the beginning."," The same concept that works on a phone could work on a physical device - maybe even better for some users.",[743,4306,4307],{},"Sometimes the best projects come from playing with your kids.",[1463,4309],{},[750,4311,4313],{"id":4312},"resources","Resources",[755,4315,4316,4323,4330,4337],{},[758,4317,4318],{},[886,4319,4322],{"href":4320,"rel":4321},"https://flutter.dev",[1340],"Flutter Documentation",[758,4324,4325],{},[886,4326,4329],{"href":4327,"rel":4328},"https://pub.dev/packages/just_audio",[1340],"just_audio Package",[758,4331,4332],{},[886,4333,4336],{"href":4334,"rel":4335},"https://www.w3.org/WAI/standards-guidelines/mobile/",[1340],"Accessibility Guidelines for Mobile Apps",[758,4338,4339],{},[886,4340,4343],{"href":4341,"rel":4342},"https://docs.espressif.com/projects/esp-adf/en/latest/",[1340],"ESP32 Audio Projects",[1463,4345],{},[750,4347,4349],{"id":4348},"for-clients-what-to-know-before-commissioning-a-mobile-app","For Clients: What to Know Before Commissioning a Mobile App",[743,4351,4352],{},"We thought it might be useful to share some honest insights from building MemoSonic - especially if you're considering commissioning a mobile app yourself.",[1496,4354,4356],{"id":4355},"timeline-reality","Timeline Reality",[743,4358,4359,4362,4363,1586],{},[761,4360,4361],{},"MemoSonic from idea to production:"," ~10 calendar months, but effectively ",[761,4364,4365],{},"2-3 weeks of intense work",[743,4367,4368],{},"Why the difference? Because projects have their own rhythm - there are pauses for testing ideas, gathering feedback, handling other priorities. Realistically:",[1168,4370,4371,4381],{},[1171,4372,4373],{},[1174,4374,4375,4378],{},[1177,4376,4377],{},"Phase",[1177,4379,4380],{},"Time",[1182,4382,4383,4391,4399,4406,4414],{},[1174,4384,4385,4388],{},[1187,4386,4387],{},"Prototype / proof of concept",[1187,4389,4390],{},"1-2 days",[1174,4392,4393,4396],{},[1187,4394,4395],{},"Core functionality",[1187,4397,4398],{},"3-5 days",[1174,4400,4401,4404],{},[1187,4402,4403],{},"UI/UX polish",[1187,4405,4398],{},[1174,4407,4408,4411],{},[1187,4409,4410],{},"Testing, bugs, deployment",[1187,4412,4413],{},"2-5 days",[1174,4415,4416,4421],{},[1187,4417,4418],{},[761,4419,4420],{},"Effective total",[1187,4422,4423],{},[761,4424,4425],{},"2-3 weeks",[1496,4427,4429],{"id":4428},"where-do-most-problems-occur","Where Do Most Problems Occur?",[743,4431,4432,4435],{},[761,4433,4434],{},"1. Audio/Media"," - Sounds simple (\"just play a sound\"), but:",[755,4437,4438,4441,4444,4447],{},[758,4439,4440],{},"Playback latency issues",[758,4442,4443],{},"Conflicts when sounds overlap",[758,4445,4446],{},"Differences between iOS and Android behavior",[758,4448,4449],{},"Library bugs (e.g., \"Message responses can be sent only once\")",[743,4451,4452,4455],{},[761,4453,4454],{},"2. iOS Builds"," - Every mobile project confirms this. Certificates, provisioning profiles, App Store review. Android is simpler.",[743,4457,4458,4461],{},[761,4459,4460],{},"3. Content and Copyright"," - Want to use a famous melody? Images from the internet? You either generate your own assets or buy licenses. (We had to remove Metallica from the project for this reason.)",[1496,4463,4465],{"id":4464},"simple-ideas-complex-threads","Simple Ideas → Complex Threads",[743,4467,4468],{},"MemoSonic is \"just\" a memory game with sounds. Sounds like a weekend project, right?",[743,4470,4471],{},"And yet:",[755,4473,4474,4480,4486,4492,4498,4504],{},[758,4475,4476,4479],{},[761,4477,4478],{},"300+ audio files"," to generate (chords, scales, rhythms, animal sounds)",[758,4481,4482,4485],{},[761,4483,4484],{},"Content generation pipeline"," - Python, FluidSynth, FFmpeg, sound synthesis",[758,4487,4488,4491],{},[761,4489,4490],{},"Two game modes"," with different logic",[758,4493,4494,4497],{},[761,4495,4496],{},"Three difficulty levels"," with balancing",[758,4499,4500,4503],{},[761,4501,4502],{},"Accessibility"," - contrast, large buttons, audio feedback",[758,4505,4506,4509],{},[761,4507,4508],{},"Card animations"," with 3D perspective",[743,4511,4512],{},"What seems like a \"simple app\" often requires:",[755,4514,4515,4518,4521,4524],{},[758,4516,4517],{},"Integration with multiple libraries",[758,4519,4520],{},"Handling edge cases",[758,4522,4523],{},"Testing on various devices",[758,4525,4526],{},"Iterations based on user feedback",[1496,4528,4530],{"id":4529},"the-golden-rule","The Golden Rule",[1308,4532,4533],{},[743,4534,4535],{},[761,4536,4537],{},"MVP in 5 days, polishing - endless.",[743,4539,4540],{},"The first working version comes together quickly. But the difference between \"it works\" and \"it works well on every device, looks professional, and doesn't crash\" - that's weeks of additional work.",[1496,4542,4544],{"id":4543},"the-bottom-line","The Bottom Line",[743,4546,4547],{},"If you're planning to build an app, budget for:",[755,4549,4550,4556,4562,4568],{},[758,4551,4552,4555],{},[761,4553,4554],{},"Time:"," 2-3x your initial estimate",[758,4557,4558,4561],{},[761,4559,4560],{},"Complexity:"," Simple features often hide complex implementations",[758,4563,4564,4567],{},[761,4565,4566],{},"Platform quirks:"," iOS, Android and web behave differently",[758,4569,4570,4573],{},[761,4571,4572],{},"Content:"," Creating or licensing assets takes time and money",[743,4575,4576,4577,4580],{},"The silver lining? ",[761,4578,4579],{},"Cross-platform frameworks like Flutter"," mean you get both iOS and Android from a single codebase. One team, one codebase, two app stores. That's a massive time and cost saver compared to building native apps separately.",[743,4582,4583],{},"The good news? With the right team and realistic expectations, even ambitious ideas can become polished products. MemoSonic started as a rainy Sunday vibe-coded game with kids. Now it's a  people train their ears.",[1463,4585],{},[1496,4587,4589],{"id":4588},"try-memosonic","Try MemoSonic",[755,4591,4592,4601,4609],{},[758,4593,4594,4597,4598],{},[761,4595,4596],{},"Web:"," ",[886,4599,4246],{"href":4244,"rel":4600},[1340],[758,4602,4603,4597,4606],{},[761,4604,4605],{},"iOS:",[886,4607,4256],{"href":4254,"rel":4608},[1340],[758,4610,4611,4597,4614],{},[761,4612,4613],{},"Android:",[886,4615,4266],{"href":4264,"rel":4616},[1340],[4618,4619,4620],"style",{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":899,"searchDepth":900,"depth":900,"links":4622},[4623,4624,4627,4631,4639,4643,4647,4648,4652,4657,4663,4664,4671,4672,4673,4674],{"id":1425,"depth":900,"text":1426},{"id":1467,"depth":900,"text":1468,"children":4625},[4626],{"id":1498,"depth":1713,"text":1499},{"id":1550,"depth":900,"text":1551,"children":4628},[4629,4630],{"id":1554,"depth":1713,"text":1555},{"id":1578,"depth":1713,"text":1579},{"id":1632,"depth":900,"text":1633,"children":4632},[4633,4634,4635,4636,4637,4638],{"id":1657,"depth":1713,"text":1658},{"id":1968,"depth":1713,"text":1969},{"id":2558,"depth":1713,"text":2559},{"id":2810,"depth":1713,"text":2811},{"id":3041,"depth":1713,"text":3042},{"id":3135,"depth":1713,"text":3136},{"id":3176,"depth":900,"text":3177,"children":4640},[4641,4642],{"id":3183,"depth":1713,"text":3184},{"id":3223,"depth":1713,"text":3224},{"id":3239,"depth":900,"text":3240,"children":4644},[4645,4646],{"id":3246,"depth":1713,"text":3247},{"id":3273,"depth":1713,"text":3274},{"id":3288,"depth":900,"text":3289},{"id":3376,"depth":900,"text":3377,"children":4649},[4650,4651],{"id":3383,"depth":1713,"text":3384},{"id":3426,"depth":1713,"text":3427},{"id":3502,"depth":900,"text":3503,"children":4653},[4654,4655,4656],{"id":3506,"depth":1713,"text":3507},{"id":3516,"depth":1713,"text":3517},{"id":3612,"depth":1713,"text":3613},{"id":3857,"depth":900,"text":3858,"children":4658},[4659,4660,4661,4662],{"id":3861,"depth":1713,"text":3862},{"id":3868,"depth":1713,"text":3869},{"id":3886,"depth":1713,"text":3887},{"id":3893,"depth":1713,"text":3894},{"id":3919,"depth":900,"text":3920},{"id":4017,"depth":900,"text":4018,"children":4665},[4666,4667,4668,4669,4670],{"id":4043,"depth":1713,"text":4044},{"id":4076,"depth":1713,"text":4077},{"id":4108,"depth":1713,"text":4109},{"id":4156,"depth":1713,"text":4157},{"id":4195,"depth":1713,"text":4196},{"id":4229,"depth":900,"text":4230},{"id":4274,"depth":900,"text":4275},{"id":4312,"depth":900,"text":4313},{"id":4348,"depth":900,"text":4349,"children":4675},[4676,4677,4678,4679,4680,4681],{"id":4355,"depth":1713,"text":4356},{"id":4428,"depth":1713,"text":4429},{"id":4464,"depth":1713,"text":4465},{"id":4529,"depth":1713,"text":4530},{"id":4543,"depth":1713,"text":4544},{"id":4588,"depth":1713,"text":4589},{"name":4683,"logo":4684},"MusicTech Lab","/images/logos/memosonic-app.png","2026-01-08T00:00:00.000Z",{"src":4687},"/images/blog/musictechlab_blog_how-we-built-memosonic-accessible-audio-memory-game-flutter.webp",{"enabled":916,"items":4689},[4690,4693,4696,4698],{"text":4691,"icon":4692},"300+ audio assets generated programmatically using Python, FluidSynth, and FFmpeg.","i-lucide-terminal",{"text":4694,"icon":4695},"Sound-first gameplay makes the app accessible to visually impaired players by design.","i-lucide-headphones",{"text":4697,"icon":926},"Effective development took 2-3 weeks despite a 10-month calendar timeline.",{"text":4699,"icon":4700},"6 categories cover chords, scales, rhythms, notes, animals, and instruments.","i-lucide-music",{},"/blog/case-studies/how-we-built-memosonic-accessible-audio-memory-game-flutter",{"title":26,"description":937},[910,4705],"development","EpQHhZ99WiwpfNwqAZy1Tpb3DU7mhR5z4TDWIwaKj0c",{"id":4708,"title":14,"authors":736,"badge":4709,"body":4710,"category":910,"client":5053,"date":5054,"description":5055,"extension":915,"faq":736,"featured":69,"featuredOrder":736,"hidden":69,"image":5056,"keyTakeaways":5058,"meta":5069,"navigation":916,"path":5070,"seo":5071,"status":736,"stem":16,"tags":5072,"teaser":736,"__hash__":5073,"score":1713},"posts/blog/case-study/chromecast-airplay-casting-app-case-study.md",{"label":5,"color":738},{"type":740,"value":4711,"toc":5040},[4712,4716,4719,4722,4725,4730,4734,4741,4744,4767,4770,4774,4777,4780,4796,4801,4805,4808,4811,4815,4818,4824,4827,4831,4848,4852,4883,4887,4890,4907,4912,4916,4980,4982,5018,5022],[750,4713,4715],{"id":4714},"the-brief-that-seemed-simple","The Brief That Seemed Simple",[743,4717,4718],{},"A media company came to us with what sounded like a straightforward request: \"We want users to browse video content on their phones and cast it to any TV nearby.\"",[743,4720,4721],{},"Simple, right? Tap a video, tap a TV, watch.",[743,4723,4724],{},"Except the word \"any\" hid a world of complexity. The client's users had Chromecasts, Apple TVs, smart TVs with AirPlay, and older devices. The content needed dynamic overlays — QR codes, logos, animations that appear at specific moments. And the whole experience had to feel instant, like the phone was just a fancy remote control.",[1025,4726,4727],{},[743,4728,4729],{},"We quickly realized this wasn't a development project. It was an R&D expedition.",[750,4731,4733],{"id":4732},"the-real-problem-control-not-just-playback","The Real Problem: Control, Not Just Playback",[743,4735,4736,4737,4740],{},"Here's what most people don't understand about Chromecast: playing a video is easy. ",[1434,4738,4739],{},"Controlling"," what happens on the TV is hard.",[743,4742,4743],{},"Chromecast was designed for a simple use case — send a URL, let the TV handle it. But our client needed more:",[961,4745,4747,4752,4757,4762],{"className":4746},[964,965,1046,967,968],[970,4748],{"description":4749,"icon":4750,"title":4751},"Toggle QR codes on and off during playback","i-lucide-qr-code","QR Code Overlays",[970,4753],{"description":4754,"icon":4755,"title":4756},"Show debug panels for testing","i-lucide-bug","Debug Information",[970,4758],{"description":4759,"icon":4760,"title":4761},"Display graphics at specific timestamps","i-lucide-layers","Custom Overlays",[970,4763],{"description":4764,"icon":4765,"title":4766},"Send commands the TV actually understands","i-lucide-send","Custom Commands",[743,4768,4769],{},"The default Chromecast receiver doesn't do any of that. We needed to build our own.",[750,4771,4773],{"id":4772},"the-platform-decision","The Platform Decision",[743,4775,4776],{},"Before writing any production code, we needed to answer a fundamental question: what technology could handle casting to multiple ecosystems while maintaining a single codebase?",[743,4778,4779],{},"We built proof-of-concept apps in three different frameworks:",[961,4781,4783,4788,4792],{"className":4782},[964,965,966,967,968],[970,4784],{"description":4785,"icon":4786,"title":4787},"Huge ecosystem, but Chromecast libraries were outdated and unreliable. After two weeks we had an app that sometimes found nearby devices. Sometimes.","i-lucide-x-circle","React Native",[970,4789],{"description":4790,"icon":4786,"title":4791},"Best casting integration, but maintaining two codebases for a startup budget wasn't realistic. More time syncing features than building them.","Native Swift/Kotlin",[970,4793],{"description":4794,"icon":4795,"title":1574},"Google's backing meant solid Chromecast support. Plugin architecture let us write native casting code when needed, keeping 90% cross-platform.","i-lucide-check-circle",[1122,4797,4798],{},[743,4799,4800],{},"We went with Flutter. It was a bet, but one that paid off.",[750,4802,4804],{"id":4803},"the-casting-puzzle-14-prototypes-later","The Casting Puzzle: 14 Prototypes Later",[743,4806,4807],{},"Here's something we don't usually admit in case studies: we built fourteen different prototypes before landing on the final architecture.",[1036,4809],{":items":4810},"[{\"title\":\"WebRTC — The Dead End\",\"description\":\"Peer-to-peer video streaming seemed ideal. We built a Node.js signaling server and got devices talking. But casting isn't a conversation — it's a broadcast. The TV doesn't need to send video back. We were overcomplicating things. Not wasted though — we extracted the QR code pairing system that became a core feature.\",\"icon\":\"i-lucide-git-branch\"},{\"title\":\"FFmpeg — The Detour\",\"description\":\"Server-side video processing to bake overlays into the stream. Built two versions (Python FastAPI + embedded Flutter). Both worked, but processing delay was noticeable and every overlay change meant re-encoding. For hundreds of videos, not sustainable.\",\"icon\":\"i-lucide-film\"},{\"title\":\"Client-Side Rendering — The Insight\",\"description\":\"Overlays needed to be rendered in real-time on top of the video stream. More complex to build, infinitely more flexible. No re-encoding, no delay.\",\"icon\":\"i-lucide-lightbulb\"},{\"title\":\"Multi-Layer Architecture — The Breakthrough\",\"description\":\"Instead of one video with baked-in graphics, a layered system: base video, overlay layer (QR codes, logos), animation layer (Lottie), subtitle layer, and audio mixing. It worked on the phone — but could we cast this to a TV?\",\"icon\":\"i-lucide-layers\"},{\"title\":\"Custom Chromecast Receiver — The Solution\",\"description\":\"We registered our own receiver with Google and built a web app running on the Chromecast itself. Custom namespace messaging, instant overlay commands, and full visual control — without touching Chromecast firmware.\",\"icon\":\"i-lucide-cast\"}]",[750,4812,4814],{"id":4813},"the-airplay-surprise","The AirPlay Surprise",[743,4816,4817],{},"AirPlay should have been easier — Apple's ecosystem, tight integration, \"it just works.\"",[743,4819,4820,4821],{},"Except Flutter had no official AirPlay plugin. ",[761,4822,4823],{},"We wrote our own.",[743,4825,4826],{},"Three weeks of diving into Apple's documentation, bridging Swift code to Dart, and testing on every AirPlay device we could find. This wasn't in the original scope, but without it, we'd have lost half our potential users.",[750,4828,4830],{"id":4829},"the-problems-nobody-warned-us-about","The Problems Nobody Warned Us About",[961,4832,4834,4839,4843],{"className":4833},[964,965,966,967,968],[970,4835],{"description":4836,"icon":4837,"title":4838},"Chromecast uses mDNS. AirPlay uses mDNS differently. Some networks block multicast. Corporate WiFi isolates devices. We built a multi-protocol discovery system with caching and graceful reconnection.","i-lucide-wifi","Network Discovery is Chaos",[970,4840],{"description":4841,"icon":1248,"title":4842},"Phone and TV are separate devices with separate states. We built a system treating the phone as source of truth for intent while respecting the TV's reality. When they diverge, graceful reconciliation.","State Synchronization",[970,4844],{"description":4845,"icon":4846,"title":4847},"A developer accidentally streamed 2GB over cellular. We added network awareness — detect WiFi-to-mobile switches, warn users, optionally pause. Small feature, saved users real money.","i-lucide-signal","The Mobile Data Problem",[750,4849,4851],{"id":4850},"what-we-actually-built","What We Actually Built",[961,4853,4855,4860,4865,4869,4874,4879],{"className":4854},[964,965,1046,967,968],[970,4856],{"description":4857,"icon":4858,"title":4859},"A web app running on the Chromecast with our overlay system, custom commands, and on-demand QR code rendering.","i-lucide-cast","Custom Chromecast Receiver",[970,4861],{"description":4862,"icon":4863,"title":4864},"Chromecast and AirPlay from the same interface. Users don't need to know which protocol their TV uses.","i-lucide-airplay","Universal Casting",[970,4866],{"description":4867,"icon":4760,"title":4868},"QR codes, logos, and animations toggled instantly. No re-encoding, no delay.","Real-Time Overlay Control",[970,4870],{"description":4871,"icon":4872,"title":4873},"Content organized into channels with D-pad navigation. Swipe up for next channel, swipe right for next video.","i-lucide-tv","Channel-Based Browsing",[970,4875],{"description":4876,"icon":4877,"title":4878},"Start on phone, cast to TV, pick up phone later — it knows what's playing. Always feels like a remote control.","i-lucide-repeat","Seamless Handoff",[970,4880],{"description":4881,"icon":4755,"title":4882},"Toggle a debug panel on the TV from your phone. Essential for testing, easy to disable in production.","Debug Mode",[750,4884,4886],{"id":4885},"the-rd-investment","The R&D Investment",[743,4888,4889],{},"Fourteen prototypes sounds like waste. It wasn't. Each failed approach taught us something:",[755,4891,4892,4895,4898,4901,4904],{},[758,4893,4894],{},"WebRTC showed us the value of QR pairing",[758,4896,4897],{},"FFmpeg experiments proved client-side rendering was necessary",[758,4899,4900],{},"React Native's limitations confirmed Flutter was the right choice",[758,4902,4903],{},"The multi-layer PoC became the foundation of our overlay system",[758,4905,4906],{},"Early Chromecast tests revealed we needed a custom receiver",[1025,4908,4909],{},[743,4910,4911],{},"The client got a production app, but they also got certainty. We didn't guess at the architecture — we proved it through systematic exploration.",[750,4913,4915],{"id":4914},"lessons-for-clients","Lessons for Clients",[1168,4917,4918,4928],{},[1171,4919,4920],{},[1174,4921,4922,4925],{},[1177,4923,4924],{},"Insight",[1177,4926,4927],{},"Why it matters",[1182,4929,4930,4940,4950,4960,4970],{},[1174,4931,4932,4937],{},[1187,4933,4934],{},[761,4935,4936],{},"Casting isn't streaming",[1187,4938,4939],{},"Sending video to a TV is easy. Controlling what happens on that TV requires custom development",[1174,4941,4942,4947],{},[1187,4943,4944],{},[761,4945,4946],{},"Custom receivers unlock everything",[1187,4948,4949],{},"Anything beyond play/pause/seek means building your own Chromecast receiver",[1174,4951,4952,4957],{},[1187,4953,4954],{},[761,4955,4956],{},"Platform choice matters enormously",[1187,4958,4959],{},"Two weeks on React Native felt like lost time, but it would have been months of pain",[1174,4961,4962,4967],{},[1187,4963,4964],{},[761,4965,4966],{},"Prototypes aren't waste",[1187,4968,4969],{},"Every PoC either became part of the final product or eliminated a dead end early",[1174,4971,4972,4977],{},[1187,4973,4974],{},[761,4975,4976],{},"Test on real hardware",[1187,4978,4979],{},"Chromecast bugs don't show up in simulators. We maintained a \"casting corner\" with multiple device generations",[750,4981,1080],{"id":1079},[961,4983,4985,4988,4992,4996,5001,5006,5011,5015],{"className":4984},[964,1084,966,1085,1086,968],[970,4986],{"description":4987,"icon":977,"title":1574},"Cross-platform mobile",[970,4989],{"description":4990,"icon":4858,"title":4991},"Custom receiver app","Chromecast SDK",[970,4993],{"description":4994,"icon":4863,"title":4995},"Custom Flutter plugin","AirPlay",[970,4997],{"description":4998,"icon":4999,"title":5000},"QR-based device pairing","i-lucide-video","WebRTC",[970,5002],{"description":5003,"icon":5004,"title":5005},"Video processing","i-lucide-film","FFmpeg",[970,5007],{"description":5008,"icon":5009,"title":5010},"Animation overlays","i-lucide-sparkles","Lottie",[970,5012],{"description":5013,"icon":973,"title":5014},"Signaling server","Node.js",[970,5016],{"description":5017,"icon":926,"title":1287},"Video processing API",[750,5019,5021],{"id":5020},"deliverables","Deliverables",[961,5023,5025,5029,5032,5036],{"className":5024},[964,965,1046,967,968],[970,5026],{"description":5027,"icon":977,"title":5028},"iOS + Android from a single Flutter codebase","Cross-Platform Mobile App",[970,5030],{"description":5031,"icon":4858,"title":4859},"With overlay support and custom messaging protocol",[970,5033],{"description":5034,"icon":4863,"title":5035},"Built from scratch for Flutter","Custom AirPlay Plugin",[970,5037],{"description":5038,"icon":1075,"title":5039},"Content and overlay management","Backend CMS",{"title":899,"searchDepth":900,"depth":900,"links":5041},[5042,5043,5044,5045,5046,5047,5048,5049,5050,5051,5052],{"id":4714,"depth":900,"text":4715},{"id":4732,"depth":900,"text":4733},{"id":4772,"depth":900,"text":4773},{"id":4803,"depth":900,"text":4804},{"id":4813,"depth":900,"text":4814},{"id":4829,"depth":900,"text":4830},{"id":4850,"depth":900,"text":4851},{"id":4885,"depth":900,"text":4886},{"id":4914,"depth":900,"text":4915},{"id":1079,"depth":900,"text":1080},{"id":5020,"depth":900,"text":5021},{"nda":916},"2025-01-13T00:00:00.000Z","How controlling a TV turned out to be harder than streaming to it, and why we built our own Chromecast receiver after 14 prototypes.",{"src":5057,"hasLogo":69},"/images/blog/chromecast-airplay-casting-app-case-study.webp",{"enabled":916,"items":5059},[5060,5063,5065,5067],{"text":5061,"icon":5062},"14 prototypes built before finding the right casting architecture.","i-lucide-blocks",{"text":5064,"icon":4858},"Custom Chromecast receiver enables real-time QR code and animation overlays on TV.",{"text":5066,"icon":997},"Custom AirPlay Flutter plugin written from scratch in 3 weeks.",{"text":5068,"icon":977},"Flutter chosen over React Native and native after PoC comparison across all three.",{},"/blog/case-studies/chromecast-airplay-casting-app-case-study",{"title":14,"description":5055},[910,4705],"rp1YICin9SG9v9J2bLejUif7lpAB5aZe85kWeFkRR6A",1780305221777]